diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7263bb4..b378a33b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ env: on: pull_request: push: - branches: ['master'] + branches: ['master', 'series/2.x'] release: types: - published @@ -47,11 +47,12 @@ jobs: strategy: fail-fast: false matrix: - java: ['adopt@1.8'] - scala: ['2.11.12', '2.12.15', '2.13.7', '3.1.1'] + java: ['adopt@1.8', '11', '17'] + scala: ['2.12.15', '2.13.11', '3.3.0'] + platform: ['JVM', 'Native'] steps: - name: Checkout current branch - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.3.0 with: fetch-depth: 0 - name: Setup Scala and Java @@ -60,37 +61,20 @@ jobs: java-version: ${{ matrix.java }} - name: Cache scala dependencies uses: coursier/cache-action@v6 - - name: Test 2.x - if: ${{ !startsWith(matrix.scala, '3.') }} - run: ./sbt ++${{ matrix.scala }}! test - - name: Test 3.x - if: ${{ startsWith(matrix.scala, '3.') }} - run: ./sbt ++${{ matrix.scala }}! testDotty - - testJvms: - runs-on: ubuntu-20.04 - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - java: ['11', '17'] - steps: - - name: Checkout current branch - uses: actions/checkout@v3.0.0 - with: - fetch-depth: 0 - - name: Setup Scala and Java - uses: olafurpg/setup-scala@v13 + - name: Install libuv + if: matrix.platform == 'Native' + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Set Swap Space + if: matrix.platform == 'Native' + uses: pierotofy/set-swap-space@master with: - java-version: ${{ matrix.java }} - - name: Cache scala dependencies - uses: coursier/cache-action@v6 - - name: Test - run: ./sbt test + swap-size-gb: 9 + - name: Tests + run: ./sbt ++${{ matrix.scala }} zioNio${{ matrix.platform }}/test ci: runs-on: ubuntu-20.04 - needs: [lint, mdoc, test, testJvms] + needs: [lint, mdoc, test] steps: - name: Report successful build run: echo "ci passed" diff --git a/.gitignore b/.gitignore index 6673d46a..0058ae60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,37 @@ +project/zecret +project/travis-deploy-key +project/secrets.tar.xz target +test-output/ .sbtopts +.bsp project/.sbt -*.tmp -website/i18n -website/yarn.lock -website/static/api +test-output/ +.bloop +.metals +metals.sbt +*/metals.sbt +.idea +coursier +.DS_Store +metals.sbt +project/metals.sbt +project/project/metals.sbt +sbt.json +.bsp/ +project/project/ +*.iml + # if you are here to add your IDE's files please read this instead: # https://stackoverflow.com/questions/7335420/global-git-ignore#22885996 +website/node_modules +website/.docusaurus +website/build +website/docs +website/static/api* +website/versioned_docs +website/i18n/en.json +website/yarn.lock +website/package-lock.json +website/static/api +.bsp/ diff --git a/.nvmrc b/.nvmrc index 832d3850..cb406c60 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.14.0 +16.20.2 diff --git a/.scalafix.conf b/.scalafix.conf index b090394c..c00d726e 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1,27 +1,8 @@ rules = [ - Disable DisableSyntax - ExplicitResultTypes LeakingImplicitClassVal NoAutoTupling NoValInForComprehension - OrganizeImports ProcedureSyntax RemoveUnused -] - -Disable { - ifSynthetic = [ - "scala/Option.option2Iterable" - "scala/Predef.any2stringadd" - ] -} - -OrganizeImports { - # Allign with IntelliJ IDEA so that they don't fight each other - groupedImports = Merge -} - -RemoveUnused { - imports = false // handled by OrganizeImports -} +] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e72490fb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.watcherExclude": { + "**/target": true + } +} \ No newline at end of file diff --git a/build.sbt b/build.sbt index a4bb453e..6cfbbc43 100644 --- a/build.sbt +++ b/build.sbt @@ -7,7 +7,10 @@ inThisBuild( licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), developers := List( Developer("jdegoes", "John De Goes", "john@degoes.net", url("http://degoes.net")) - ) + ), + scalaVersion := "2.13.11", + semanticdbEnabled := true, + semanticdbVersion := scalafixSemanticdb.revision ) ) @@ -19,22 +22,41 @@ addCommandAlias( ";zioNio/test;examples/test" ) -val zioVersion = "1.0.12" +val zioVersion = "2.0.16" + +lazy val root = crossProject(JVMPlatform, NativePlatform) + .in(file(".")) + .settings(publish / skip := true) + .aggregate(zioNio, examples) -lazy val zioNio = project +lazy val zioNio = crossProject(JVMPlatform, NativePlatform) .in(file("nio")) .settings(stdSettings("zio-nio")) - .settings( + .settings(crossProjectSettings) + .settings(buildInfoSettings("zio.nio")) + .settings(scala3Settings) + .jvmSettings( libraryDependencies ++= Seq( "dev.zio" %% "zio" % zioVersion, "dev.zio" %% "zio-streams" % zioVersion, - "org.scala-lang.modules" %% "scala-collection-compat" % "2.5.0", + "org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0", "dev.zio" %% "zio-test" % zioVersion % Test, "dev.zio" %% "zio-test-sbt" % zioVersion % Test ), testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")) ) - .settings(dottySettings) + .nativeSettings(Test / fork := false) + .nativeSettings( + libraryDependencies ++= Seq( + "dev.zio" %%% "zio" % zioVersion, + "dev.zio" %%% "zio-streams" % zioVersion, + "org.scala-lang.modules" %%% "scala-collection-compat" % "2.11.0", + "dev.zio" %%% "zio-test" % zioVersion % Test, + "dev.zio" %%% "zio-test-sbt" % zioVersion % Test, + "io.github.cquiroz" %%% "scala-java-time" % "2.5.0" + ), + testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")) + ) lazy val docs = project .in(file("zio-nio-docs")) @@ -43,21 +65,23 @@ lazy val docs = project moduleName := "zio-nio-docs", scalacOptions -= "-Yno-imports", scalacOptions -= "-Xfatal-warnings", - ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(zioNio), + ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(zioNio.jvm), ScalaUnidoc / unidoc / target := (LocalRootProject / baseDirectory).value / "website" / "static" / "api", cleanFiles += (ScalaUnidoc / unidoc / target).value, docusaurusCreateSite := docusaurusCreateSite.dependsOn(Compile / unidoc).value, docusaurusPublishGhpages := docusaurusPublishGhpages.dependsOn(Compile / unidoc).value ) - .dependsOn(zioNio) + .dependsOn(zioNio.jvm) .enablePlugins(MdocPlugin, DocusaurusPlugin, ScalaUnidocPlugin) -lazy val examples = project +lazy val examples = crossProject(JVMPlatform, NativePlatform) .in(file("examples")) .settings(stdSettings("examples")) .settings( publish / skip := true, moduleName := "examples" ) - .settings(dottySettings) + .settings(crossProjectSettings) + .settings(buildInfoSettings("examples")) + .settings(scala3Settings) .dependsOn(zioNio) diff --git a/docs/essentials/blocking.md b/docs/essentials/blocking.md index abd6d6dc..8193ff5d 100644 --- a/docs/essentials/blocking.md +++ b/docs/essentials/blocking.md @@ -11,15 +11,15 @@ Many NIO operations can block the calling thread when called. ZIO-NIO provides A ## Blocking and Non-Blocking Channel Operations -Channel APIs that may block are not exposed on the channel itself. They are accessed via the channel's `useBlocking` method. You provide this method a function that excepts a `BlockingOps` object and returns a `ZIO` effect value. The `BlockingOps` parameter will be appropriate to the type of channel and has the actual blocking I/O effects such as read and write. +Channel APIs that may block are not exposed on the channel itself. They are accessed via the channel's `flatMapBlocking` method. You provide this method a function that excepts a `BlockingOps` object and returns a `ZIO` effect value. The `BlockingOps` parameter will be appropriate to the type of channel and has the actual blocking I/O effects such as read and write. -The `useBlocking` method performs some setup required for safe use of blocking NIO APIs: +The `flatMapBlocking` method performs some setup required for safe use of blocking NIO APIs: * Puts the channel in blocking mode * Runs the resulting effect value on ZIO's blocking thread pool, leaving the standard pool unblocked. * Installs interrupt handling, so the channel will be closed if the ZIO fiber is interrupted. This unblocks the blocked I/O operation. (Note that NIO does not offer a way to interrupt a blocked I/O operation on a channel that does not close the channel). -Non-blocking usage does not require this special handling, but for consistency the non-blocking operations are accessed in a similar way by calling `useNonBlocking` on the channel. For some channels there are some small differences between the blocking and non-blocking APIs. For example, `SocketChannel` only offers the `finishConnect` operation in the non-blocking case, as it is never needed in blocking mode. +Non-blocking usage does not require this special handling, but for consistency the non-blocking operations are accessed in a similar way by calling `flatMapNonBlocking` on the channel. For some channels there are some small differences between the blocking and non-blocking APIs. For example, `SocketChannel` only offers the `finishConnect` operation in the non-blocking case, as it is never needed in blocking mode. ```scala mdoc:silent import zio.ZIO @@ -32,36 +32,36 @@ def readHeader(c: SocketChannel): ZIO[Blocking, IOException, (Chunk[Byte], Chunk } ``` -### Using Managed Channels +### Using Channels -To help with the common use-case where you want to create a channel, there is versions of `useBlocking` and `useNonBlocking` that can be called directly on a managed value providing a channel. +To help with the common use-case where you want to create a channel, there is versions of `flatMapBlocking` and `flatMapNonBlocking` that can be called directly on a ZIO value providing a channel. -`useNioBlocking` provides both the channel and the requested type of operations: +`flatMapNioBlocking` provides both the channel and the requested type of operations: ```scala mdoc:silent import zio.nio._ import zio.nio.channels._ -SocketChannel.open.useNioBlocking { (channel, blockingOps) => +SocketChannel.open.flatMapNioBlocking { (channel, blockingOps) => blockingOps.readChunk(100) <*> channel.remoteAddress } ``` -If you don't need the channel, there's `useNioBlockingOps`: +If you don't need the channel, there's `flatMapNioBlockingOps`: ```scala mdoc:silent import zio.nio.channels._ -SocketChannel.open.useNioBlockingOps { blockingOps => +SocketChannel.open.flatMapNioBlockingOps { blockingOps => blockingOps.readChunk(100) } ``` -To use the channel in non-blocking mode, there's corresponding `useNioNonBlocking` and `useNioNonBlockingOps` methods. +To use the channel in non-blocking mode, there's corresponding `flatMapNioNonBlocking` and `flatMapNioNonBlockingOps` methods. ### Avoiding Asynchronous Boundaries -If you have a complex program that makes more than one call to `useBlocking`, then it may be worth running *all* of the ZIO-NIO parts using the blocking pool. This can be done by wrapping the effect value with your ZIO-NIO operations in `zio.blocking.blocking`. +If you have a complex program that makes more than one call to `flatMapBlocking`, then it may be worth running *all* of the ZIO-NIO parts using the blocking pool. This can be done by wrapping the effect value with your ZIO-NIO operations in `zio.blocking.blocking`. If this isn't done, you can end up with the calls using `BlockingOps` running on a thread from the blocking pool, while the other parts run on a thread from the standard pool. This involves an "asynchronous boundary" whever the fiber changes the underlying thread it's running on, which imposes some overheads including a full memory barrier. By using `zio.blocking.blocking` up-front, all the code can run on the same thread from the blocking pool. @@ -71,7 +71,7 @@ There are three main styles of channel available: blocking, non-blocking and asy ### Blocking Channels -Easy to use, with a straight-forward operation. The downsides are that you have to use `useBlocking`, which creates a new thread, and will create an additional thread for every forked fiber subsequently created. Essentially you have a blocked thread for every active I/O call, which limits scalability. Also, the additional interrupt handling logic imposes a small overhead. +Easy to use, with a straight-forward operation. The downsides are that you have to use `flatMapBlocking`, which creates a new thread, and will create an additional thread for every forked fiber subsequently created. Essentially you have a blocked thread for every active I/O call, which limits scalability. Also, the additional interrupt handling logic imposes a small overhead. ### Non-Blocking Channels @@ -85,6 +85,6 @@ The other issue is that only network channels and pipes support non-blocking mod Asynchronous channels give us what we want: we don't need a `Selector` to use them, and our thread will never block when we use them. -However, it should be noted that asynchronous file I/O is not currently possible on the JVM. `AsynchronousFileChannel` is performing blocking I/O using a pool of blocked threads, which exactly what `useBlocking` does, and shares the same drawbacks. It may be preferable to use a standard `FileChannel`, as you'll have more visibility and control over what's going on. +However, it should be noted that asynchronous file I/O is not currently possible on the JVM. `AsynchronousFileChannel` is performing blocking I/O using a pool of blocked threads, which exactly what `flatMapBlocking` does, and shares the same drawbacks. It may be preferable to use a standard `FileChannel`, as you'll have more visibility and control over what's going on. The asynchronous socket channels do *appear* to use non-blocking I/O, although they also have some form of internal thread pool as well. These should scale roughly as well as non-blocking channels. One downside is that there is no asynchronous datagram channel. diff --git a/docs/essentials/charsets.md b/docs/essentials/charsets.md index a0a8d90a..1863e7db 100644 --- a/docs/essentials/charsets.md +++ b/docs/essentials/charsets.md @@ -54,24 +54,23 @@ import zio.nio.channels.FileChannel import zio.nio.channels._ import zio.nio.file.Path import zio.stream.ZStream -import zio.blocking.Blocking -import zio.console +import zio.Console import zio.ZIO // dump a file encoded in ISO8859 to the console -FileChannel.open(Path("iso8859.txt")).useNioBlockingOps { fileOps => - val inStream: ZStream[Blocking, Exception, Byte] = ZStream.repeatEffectChunkOption { +FileChannel.open(Path("iso8859.txt")).flatMapNioBlockingOps { fileOps => + val inStream: ZStream[Any, Exception, Byte] = ZStream.repeatZIOChunkOption { fileOps.readChunk(1000).asSomeError.flatMap { chunk => if (chunk.isEmpty) ZIO.fail(None) else ZIO.succeed(chunk) } } // apply decoding transducer - val charStream: ZStream[Blocking, Exception, Char] = - inStream.transduce(Charset.Standard.iso8859_1.newDecoder.transducer()) + val charStream: ZStream[Any, Exception, Char] = + inStream.via(Charset.Standard.iso8859_1.newDecoder.transducer()) - console.putStrLn("ISO8859 file contents:") *> - charStream.foreachChunk(chars => console.putStr(chars.mkString)) + Console.printLine("ISO8859 file contents:") *> + charStream.runForeachChunk(chars => Console.printLine(chars.mkString)) } ``` diff --git a/docs/essentials/files.md b/docs/essentials/files.md index 8c589bb1..b9352a07 100644 --- a/docs/essentials/files.md +++ b/docs/essentials/files.md @@ -11,23 +11,25 @@ Required imports for presented snippets: import zio._ import zio.nio.channels._ import zio.nio.file._ -import zio.console._ +import zio.Console._ ``` ## Basic operations -Opening a file for a given path (with no additional open attributes) returns a `ZManaged` instance on which we're running the intended operations. `ZManaged` makes sure that the channel gets closed afterwards: +Opening a file for a given path (with no additional open attributes) returns a scoped `ZIO` instance on which we're running the intended operations. `Scope` makes sure that the channel gets closed afterwards: ```scala mdoc:silent import java.nio.file.StandardOpenOption val path = Path("file.txt") -val channelM = AsynchronousFileChannel.open( - path, - StandardOpenOption.READ, - StandardOpenOption.WRITE -).use { channel => - readWriteOp(channel) *> lockOp(channel) +val channelM = ZIO.scoped { + AsynchronousFileChannel.open( + path, + StandardOpenOption.READ, + StandardOpenOption.WRITE + ).flatMap { channel => + readWriteOp(channel) *> lockOp(channel) + } } ``` @@ -38,7 +40,7 @@ val readWriteOp = (channel: AsynchronousFileChannel) => for { chunk <- channel.readChunk(20, 0L) text = chunk.map(_.toChar).mkString - _ <- putStrLn(text) + _ <- printLine(text) input = Chunk.fromArray("message".toArray.map(_.toByte)) _ <- channel.writeChunk(input, 0L) @@ -51,14 +53,14 @@ they are not in effects. Apart from basic acquire/release actions, the core API ```scala mdoc:silent val lockOp = (channel: AsynchronousFileChannel) => for { - isShared <- channel.lock().bracket(_.release.ignore)(l => IO.succeed(l.isShared)) - _ <- putStrLn(isShared.toString) // false + isShared <- ZIO.acquireReleaseWith(channel.lock())(_.release.ignore)(l => ZIO.succeed(l.isShared)) + _ <- printLine(isShared.toString) // false - managed = Managed.make(channel.lock(position = 0, size = 10, shared = false))(_.release.ignore) - isOverlaping <- managed.use(l => IO.succeed(l.overlaps(5, 20))) - _ <- putStrLn(isOverlaping.toString) // true + scoped = ZIO.acquireRelease(channel.lock(position = 0, size = 10, shared = false))(_.release.ignore) + isOverlaping <- ZIO.scoped(scoped.flatMap(l => ZIO.succeed(l.overlaps(5, 20)))) + _ <- printLine(isOverlaping.toString) // true } yield () ``` Also it's worth mentioning that we are treating `FileLock` as a resource here. -For demonstration purposes we handled it in two different ways: using `bracket` and creating `Managed` for this. +For demonstration purposes we handled it in two different ways: using `acquireRelease` and creating a scoped `ZIO` for this. diff --git a/docs/essentials/index.md b/docs/essentials/index.md index 4bde51c5..f2473071 100644 --- a/docs/essentials/index.md +++ b/docs/essentials/index.md @@ -27,16 +27,17 @@ When reading from channels, the end of the stream may be reached at any time. Th ```scala mdoc:silent import zio._ -import zio.blocking.Blocking import zio.nio._ import zio.nio.channels._ import zio.nio.file.Path import java.io.IOException -val read100: ZIO[Blocking, Option[IOException], Chunk[Byte]] = - FileChannel.open(Path("foo.txt")) - .useNioBlockingOps(_.readChunk(100)) - .eofCheck +val read100: ZIO[Any, Option[IOException], Chunk[Byte]] = + ZIO.scoped { + FileChannel.open(Path("foo.txt")) + .flatMapNioBlockingOps(_.readChunk(100)) + .eofCheck + } ``` End-of-stream will be signalled with `None`. Any errors will be wrapped in `Some`. diff --git a/docs/essentials/resources.md b/docs/essentials/resources.md index 389fd463..7c816622 100644 --- a/docs/essentials/resources.md +++ b/docs/essentials/resources.md @@ -5,62 +5,55 @@ title: "Resource Management" NIO offers several objects, primarily channels, that consume resources (such as operating system file handles) that need to be released when no longer needed. If channels are not closed reliably, resource leaks can occur, causing a number of issues. -For this reason, ZIO-NIO provides such resources using the [ZIO `ZManaged` API][zio-managed]. For example, calling `FileChannel.open` will produce a value of `ZManaged[Blocking, IOException, FileChannel]`. The file will not actually be opened until the managed value is *used*. +For this reason, ZIO-NIO provides such resources using the [ZIO `Scope` API][zio-scope]. For example, calling `FileChannel.open` will produce a value of `ZIO[Scope, IOException, FileChannel]`. The file will automatically be closed when the scope is closed. ## Simple Usage -The most straight-forward way to use a managed resource is with the `use` method: +The most straight-forward way to use a scoped resource is with the `scoped` method: ```scala mdoc:silent import zio._ -import zio.blocking.Blocking import zio.nio.channels._ import zio.nio.file.Path import java.io.IOException -def useChannel(f: FileChannel): ZIO[Blocking, IOException, Unit] = ??? +def useChannel(f: FileChannel): ZIO[Any, IOException, Unit] = ??? -val effect: ZIO[Blocking, IOException, Unit] = FileChannel.open(Path("foo.txt")) - .use { fileChannel => - // fileChannel is only valid in this lexical scope - useChannel(fileChannel) - } +val effect: ZIO[Any, IOException, Unit] = ZIO.scoped { + FileChannel.open(Path("foo.txt")) + .flatMap { fileChannel => + // fileChannel is only valid in the scope + useChannel(fileChannel) + } +} ``` -In the above example, the `FileChannel` will be opened and then provided to the function passed to `use`. The channel will always be closed when the `use` function completes, regardless of whether the operation succeeds, fails, dies or is interrupted. As long as the channel is only used within the function passed to `use`, then we're guaranteed not to have leaks. +In the above example, the `FileChannel` will be opened and then provided to the `useChannel` operation. The channel will always be closed when the scope is closed, regardless of whether the operation succeeds, fails, dies or is interrupted. As long as the channel is only used within the `Scope`, then we're guaranteed not to have leaks. ## Flexible Resource Scoping -Sometimes there are situations where `ZManaged#use` is too limiting, because the resource lifecycle needs to extend beyond a lexical scope. An example of this is registering channels with a `Selector`. How can we do this using `ZManaged` while still avoiding the possibility of leaks? One way is to use [the "scope" feature of `ZManaged`][zio-scope]. +Sometimes the resource lifecycle needs to extend beyond a lexical scope. An example of this is registering channels with a `Selector`. How can we do this using `Scope` while still avoiding the possibility of leaks?. -A scope is itself a managed resource. Other managed resources can be attached to a scope, which gives them the same lifecycle as the scope. When the scope is released, all the other resources that have been attached to it will also be released. +We can access the current scope using the `ZIO.scope` operator. The lifetime of other resources can be extended into this scope using the `Scope#extend` operator. When the scope is closed, all the other resources whose lifetimes have been extended into the scope will also be finalized. ```scala mdoc:silent -ZManaged.scope.use { scope => +ZIO.scope.flatMap { scope => - val channel: IO[IOException, SocketChannel] = scope(SocketChannel.open).map { - case (earlyRelease @ _, channel) => channel - } + val channel: IO[IOException, SocketChannel] = scope.extend(SocketChannel.open) // use channel, perhaps with a Selector - channel.flatMap(_.useNonBlocking(_.readChunk(10))) + channel.flatMap(_.flatMapNonBlocking(_.readChunk(10))) } -// the scope has now been released, as have all the resources attached to it +// when the scope is closed all resources whose lifetimes have been extended into it will automatically be finalized. ``` -Note that `scope` returns both the resource and an "early release" effect. This allows you to release the resource before the scope exits, if you know it is no longer needed. This allows efficient use of the resource while still having the safety net of the scope to ensure the release happens even if there are failures, defects or interruptions. +You can continue to finalize the resource before the scope is closed, if you know it is no longer needed. This allows efficient use of the resource while still having the safety net of the scope to ensure the finalization happens even if there are failures, defects or interruptions. The `zio.nio.channels.SelectorSpec` test demonstrates the use of scoping to ensure nothing leaks if an error occurs. ### Using `close` for Early Release -In the case of channels, we don't actually need the early release features that `ZManaged` provides, as every channel has a built-in early release in the form of the `close` method. Closing a channel more than once is a perfectly safe thing to do, so you can use `close` to release a channel's resources early. When the `ZManaged` scope of the channel later ends, `close` will be called again, but it will be a no-op. - -## Manual Resource Management - -It is also possible to switch to completely manual resource management. [The `reserve` method][zio-reserve] can be called on any `ZManaged` value, which gives you the acquisition and release of the resource as two separate effect values that you can use as you like. If you use these reservation effects directly, it is entirely up to you to avoid leaking resources. This requires code to be written very carefully, and an understanding the finer details of how failures, defects and interruption work in ZIO. +Every channel has a built-in early release in the form of the `close` method. Closing a channel more than once is a perfectly safe thing to do, so you can use `close` to release a channel's resources early. When the `Scope` of the channel later ends, `close` will be called again, but it will be a no-op. -[zio-managed]: https://zio.dev/docs/datatypes/datatypes_managed -[zio-scope]: https://javadoc.io/doc/dev.zio/zio_2.13/latest/zio/ZManaged$.html#scope:zio.Managed[Nothing,zio.ZManaged.Scope] -[zio-reserve]: https://javadoc.io/doc/dev.zio/zio_2.13/latest/zio/ZManaged.html#reserve:zio.UIO[zio.Reservation[R,E,A]] +[zio-scope]: https://zio.dev/docs/datatypes/datatypes_scope diff --git a/docs/essentials/sockets.md b/docs/essentials/sockets.md index aa0b4bdf..2758c0d8 100644 --- a/docs/essentials/sockets.md +++ b/docs/essentials/sockets.md @@ -9,8 +9,7 @@ Required imports for snippets: ```scala mdoc:silent import zio._ -import zio.clock._ -import zio.console._ +import zio.Console._ import zio.nio.channels._ import zio.nio._ ``` @@ -20,32 +19,34 @@ import zio.nio._ Creating a server socket: ```scala mdoc:silent -val server = AsynchronousServerSocketChannel.open - .mapM { socket => - for { - address <- InetSocketAddress.hostName("127.0.0.1", 1337) - _ <- socket.bindTo(address) - _ <- socket.accept.preallocate.flatMap(_.use(channel => doWork(channel).catchAll(ex => putStrLn(ex.getMessage))).fork).forever.fork - } yield () - }.useForever +val server = ZIO.scoped { + AsynchronousServerSocketChannel.open + .flatMap { socket => + for { + address <- InetSocketAddress.hostName("127.0.0.1", 1337) + _ <- socket.bindTo(address) + _ <- socket.accept.flatMap(channel => doWork(channel).catchAll(ex => printLine(ex.getMessage)).fork).forever.fork + } yield () + } *> ZIO.never +} -def doWork(channel: AsynchronousSocketChannel): ZIO[Console with Clock, Throwable, Unit] = { +def doWork(channel: AsynchronousSocketChannel): ZIO[Any, Throwable, Unit] = { val process = for { chunk <- channel.readChunk(3) str = chunk.toArray.map(_.toChar).mkString - _ <- putStrLn(s"received: [$str] [${chunk.length}]") + _ <- printLine(s"received: [$str] [${chunk.length}]") } yield () - process.whenM(channel.isOpen).forever + process.whenZIO(channel.isOpen).forever } ``` Creating a client socket: ```scala mdoc:silent -val clientM: Managed[Exception, AsynchronousSocketChannel] = AsynchronousSocketChannel.open - .mapM { client => +val clientM: ZIO[Scope, Exception, AsynchronousSocketChannel] = AsynchronousSocketChannel.open + .flatMap { client => for { host <- InetAddress.localHost address <- InetSocketAddress.inetAddress(host, 2552) @@ -59,7 +60,7 @@ Reading and writing to a socket: ```scala mdoc:silent for { serverFiber <- server.fork - _ <- clientM.use(_.writeChunk(Chunk.fromArray(Array(1, 2, 3).map(_.toByte)))) + _ <- ZIO.scoped(clientM.flatMap(_.writeChunk(Chunk.fromArray(Array(1, 2, 3).map(_.toByte))))) _ <- serverFiber.join } yield () ``` diff --git a/examples/jvm/src/main/scala/StreamDirWatch.scala b/examples/jvm/src/main/scala/StreamDirWatch.scala new file mode 100644 index 00000000..878d05b6 --- /dev/null +++ b/examples/jvm/src/main/scala/StreamDirWatch.scala @@ -0,0 +1,61 @@ +package zio +package nio +package examples + +import zio.nio.file.Path +import zio.nio.file.WatchService + +import zio.{Console, ZIOAppDefault} + +import java.nio.file.{StandardWatchEventKinds, WatchEvent} + +/** + * Example of using the `ZStream` API for watching a file system directory for events. + * + * Note that on macOS the standard Java `WatchService` uses polling and so is a bit slow, and only registers at most one + * type of event for each directory member since the last poll. + */ +object StreamDirWatch extends ZIOAppDefault { + + private def watch(dir: Path) = + ZIO.scoped { + WatchService.forDefaultFileSystem.flatMap { service => + for { + _ <- dir.registerTree( + watcher = service, + events = Set( + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_DELETE + ), + maxDepth = 100 + ) + _ <- Console.printLine(s"Watching directory '$dir'") + _ <- Console.printLine("") + _ <- service.stream.foreach { key => + val eventProcess = { (event: WatchEvent[_]) => + val desc = event.kind() match { + case StandardWatchEventKinds.ENTRY_CREATE => "Create" + case StandardWatchEventKinds.ENTRY_MODIFY => "Modify" + case StandardWatchEventKinds.ENTRY_DELETE => "Delete" + case StandardWatchEventKinds.OVERFLOW => "** Overflow **" + case other => s"Unknown: $other" + } + val path = key.resolveEventPath(event).getOrElse("** PATH UNKNOWN **") + Console.printLine(s"$desc, count: ${event.count()}, $path") + } + ZIO.scoped(key.pollEventsScoped.flatMap(ZIO.foreachDiscard(_)(eventProcess))) + } + } yield () + } + } + + override def run: URIO[ZIOAppArgs, ExitCode] = + ZIO + .serviceWith[ZIOAppArgs](_.getArgs.toList.headOption) + .flatMap( + _.map(dirString => watch(Path(dirString)).exitCode) + .getOrElse(Console.printLine("A directory argument is required").exitCode) + ) + +} diff --git a/examples/jvm/src/main/scala/StreamsBasedServer.scala b/examples/jvm/src/main/scala/StreamsBasedServer.scala new file mode 100644 index 00000000..1beb8b32 --- /dev/null +++ b/examples/jvm/src/main/scala/StreamsBasedServer.scala @@ -0,0 +1,51 @@ +package zio.nio.examples + +import zio.nio.InetSocketAddress +import zio.nio.channels.AsynchronousServerSocketChannel +import zio.stream._ +import zio.{Clock, Console, ExitCode, RIO, Scope, Trace, UIO, ZIO, ZIOAppDefault, durationInt} + +object StreamsBasedServer extends ZIOAppDefault { + + def run: UIO[ExitCode] = + ZStream + .scoped(server(8080)) + .flatMap(handleConnections(_) { chunk => + Console.printLine(s"Read data: ${chunk.mkString}") *> + Clock.sleep(2.seconds) *> + Console.printLine("Done").ignore + }) + .runDrain + .orDie + .exitCode + + def server(port: Int)(implicit trace: Trace): ZIO[Scope, Exception, AsynchronousServerSocketChannel] = + for { + server <- AsynchronousServerSocketChannel.open + socketAddress <- InetSocketAddress.wildCard(port) + _ <- server.bindTo(socketAddress) + } yield server + + def handleConnections[R]( + server: AsynchronousServerSocketChannel + )(f: String => RIO[R, Unit])(implicit trace: Trace): ZStream[R, Throwable, Unit] = + ZStream + .scoped(server.accept) + .forever + .mapZIOPar[R, Throwable, Unit](16) { channel => + for { + _ <- Console.printLine("Received connection") + data <- ZStream + .fromZIOOption( + channel.readChunk(64).tap(_ => Console.printLine("Read chunk")).orElse(ZIO.fail(None)) + ) + .flattenChunks + .take(4) + .via(ZPipeline.utf8Decode) + .run(ZSink.foldLeft("")(_ + (_: String))) + _ <- channel.close + _ <- Console.printLine("Connection closed") + _ <- f(data) + } yield () + } +} diff --git a/examples/jvm/src/main/scala/ToUppercaseAsAService.scala b/examples/jvm/src/main/scala/ToUppercaseAsAService.scala new file mode 100644 index 00000000..b62866ad --- /dev/null +++ b/examples/jvm/src/main/scala/ToUppercaseAsAService.scala @@ -0,0 +1,73 @@ +package zio +package nio +package examples + +import zio._ +import zio.nio.channels.BlockingNioOps +import zio.nio.channels.{ServerSocketChannel, SocketChannel} + +import zio.nio.charset.Charset +import zio.stream._ + +import java.io.IOException +import scala.util.control.Exception._ + +/** + * `toUpperCase` as a service. + * + * Using ZIO-NIO and ZIO streams to build a very silly TCP service. Listens on port 7777 by default. + * + * Send it UTF-8 text and it will send back the uppercase version. Amazing! + */ +object ToUppercaseAsAService extends ZIOAppDefault { + + private val upperCaseIfier: ZPipeline[Any, Nothing, Char, Char] = + ZPipeline.identity[Char] >>> ZPipeline.map[Char, Char](_.toUpper) + + private def handleConnection( + socket: SocketChannel + )(implicit trace: Trace): ZIO[Any, IOException, Long] = { + + // this does the processing of the characters received over the channel via a pipeline + // the stream of bytes from the channel is piped, then written back to the same channel's sink + def transducer: ZPipeline[Any, IOException, Byte, Byte] = + Charset.Standard.utf8.newDecoder.transducer() >>> + upperCaseIfier >>> + Charset.Standard.utf8.newEncoder.transducer() + Console.printLine("Connection accepted") *> + socket.flatMapBlocking { ops => + ops + .stream() + .via(transducer) + .run(ops.sink()) + .tapBoth( + e => Console.printLine(s"Connection error: $e"), + i => Console.printLine(s"Connection ended, wrote $i bytes") + ) + } + } + + override def run: URIO[ZIOAppArgs, ExitCode] = { + val portEff = ZIO.serviceWith[ZIOAppArgs]( + _.getArgs.toList.headOption + .flatMap(s => catching(classOf[IllegalArgumentException]).opt(s.toInt)) + .getOrElse(7777) + ) + + portEff + .flatMap(port => + ZIO.scoped { + ServerSocketChannel.open.flatMapNioBlocking { (serverChannel, ops) => + InetSocketAddress.wildCard(port).flatMap { socketAddress => + serverChannel.bindTo(socketAddress) *> + Console.printLine(s"Listening on $socketAddress") *> + ops.acceptAndFork(handleConnection).forever + } + } + } + ) + .exitCode + + } + +} diff --git a/examples/shared/src/main/scala/TextFileDump.scala b/examples/shared/src/main/scala/TextFileDump.scala new file mode 100644 index 00000000..156ffa85 --- /dev/null +++ b/examples/shared/src/main/scala/TextFileDump.scala @@ -0,0 +1,55 @@ +package zio +package nio +package examples + +import zio.nio.channels.{BlockingNioOps, FileChannel} +import zio.nio.charset.Charset +import zio.nio.file.Path +import zio.stream.ZStream + +/** + * Dumps a text file to the console using a specified encoding. + * + * Two command line parameters must be provided: + * 1. The path of the file to dump 2. The character encoding to use — optional, defaults to UTF-8 + */ +object TextFileDump extends ZIOAppDefault { + + override def run: URIO[ZIOAppArgs, ExitCode] = { + val charsetEff = ZIO.serviceWith[ZIOAppArgs](s => + (s.getArgs.toList match { + case _ :: s :: _ => Some(s) + case _ => None + }).flatMap(Charset.forNameIfSupported).getOrElse(Charset.Standard.utf8) + ) + + val program = for { + fileArg <- + ZIO.serviceWith[ZIOAppArgs](_.getArgs.toSeq.headOption).someOrFail(new Exception("File name must be specified")) + charset <- charsetEff + _ <- dump(charset, Path(fileArg)) + } yield () + + program.exitCode + } + + private def dump(charset: Charset, file: Path)(implicit + trace: Trace + ): ZIO[Any, Exception, Unit] = + ZIO.scoped { + FileChannel.open(file).flatMapNioBlockingOps { fileOps => + val inStream: ZStream[Any, Exception, Byte] = ZStream.repeatZIOChunkOption { + fileOps.readChunk(1000).asSomeError.flatMap { chunk => + if (chunk.isEmpty) ZIO.fail(None) else ZIO.succeed(chunk) + } + } + + // apply decoding pipeline + val charStream: ZStream[Any, Exception, Char] = + inStream.via(charset.newDecoder.transducer()) + + charStream.runForeachChunk(chars => Console.print(chars.mkString)) + } + } + +} diff --git a/examples/src/main/scala/StreamDirWatch.scala b/examples/src/main/scala/StreamDirWatch.scala deleted file mode 100644 index 4f4481a2..00000000 --- a/examples/src/main/scala/StreamDirWatch.scala +++ /dev/null @@ -1,53 +0,0 @@ -package zio -package nio -package examples - -import zio.nio.file.{Path, WatchService} - -import java.nio.file.{StandardWatchEventKinds, WatchEvent} - -/** - * Example of using the `ZStream` API for watching a file system directory for events. - * - * Note that on macOS the standard Java `WatchService` uses polling and so is a bit slow, and only registers at most one - * type of event for each directory member since the last poll. - */ -object StreamDirWatch extends App { - - private def watch(dir: Path) = - WatchService.forDefaultFileSystem.use { service => - for { - _ <- dir.registerTree( - watcher = service, - events = Set( - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE - ), - maxDepth = 100 - ) - _ <- console.putStrLn(s"Watching directory '$dir'") - _ <- console.putStrLn("") - _ <- service.stream.foreach { key => - val eventProcess = { (event: WatchEvent[_]) => - val desc = event.kind() match { - case StandardWatchEventKinds.ENTRY_CREATE => "Create" - case StandardWatchEventKinds.ENTRY_MODIFY => "Modify" - case StandardWatchEventKinds.ENTRY_DELETE => "Delete" - case StandardWatchEventKinds.OVERFLOW => "** Overflow **" - case other => s"Unknown: $other" - } - val path = key.resolveEventPath(event).getOrElse("** PATH UNKNOWN **") - console.putStrLn(s"$desc, count: ${event.count()}, $path") - } - key.pollEventsManaged.use(ZIO.foreach_(_)(eventProcess)) - } - } yield () - } - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = - args.headOption - .map(dirString => watch(Path(dirString)).exitCode) - .getOrElse(console.putStrLn("A directory argument is required").exitCode) - -} diff --git a/examples/src/main/scala/StreamsBasedServer.scala b/examples/src/main/scala/StreamsBasedServer.scala deleted file mode 100644 index 10d8c3a9..00000000 --- a/examples/src/main/scala/StreamsBasedServer.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio.examples - -import zio._ -import zio.clock.Clock -import zio.duration._ -import zio.nio.InetSocketAddress -import zio.nio.channels.AsynchronousServerSocketChannel -import zio.stream._ - -object StreamsBasedServer extends App { - - def run(args: List[String]): URIO[zio.console.Console with Clock with zio.console.Console, ExitCode] = - ZStream - .managed(server(8080)) - .flatMap(handleConnections(_) { chunk => - console.putStrLn(s"Read data: ${chunk.mkString}") *> - clock.sleep(2.seconds) *> - console.putStrLn("Done").ignore - }) - .runDrain - .orDie - .exitCode - - def server(port: Int): Managed[Exception, AsynchronousServerSocketChannel] = - for { - server <- AsynchronousServerSocketChannel.open - socketAddress <- InetSocketAddress.wildCard(port).toManaged_ - _ <- server.bindTo(socketAddress).toManaged_ - } yield server - - def handleConnections[R <: console.Console]( - server: AsynchronousServerSocketChannel - )(f: String => RIO[R, Unit]): ZStream[R, Throwable, Unit] = - ZStream - .repeatEffect(server.accept.preallocate) - .map(conn => ZStream.managed(conn.ensuring(console.putStrLn("Connection closed").ignore).withEarlyRelease)) - .flatMapPar[R, Throwable, Unit](16) { connection => - connection.mapM { case (closeConn, channel) => - for { - _ <- console.putStrLn("Received connection") - data <- ZStream - .fromEffectOption( - channel.readChunk(64).tap(_ => console.putStrLn("Read chunk")).orElse(ZIO.fail(None)) - ) - .flattenChunks - .take(4) - .transduce(ZTransducer.utf8Decode) - .run(Sink.foldLeft("")(_ + (_: String))) - _ <- closeConn - _ <- f(data) - } yield () - } - } - -} diff --git a/examples/src/main/scala/TextFileDump.scala b/examples/src/main/scala/TextFileDump.scala deleted file mode 100644 index efa69aa7..00000000 --- a/examples/src/main/scala/TextFileDump.scala +++ /dev/null @@ -1,49 +0,0 @@ -package zio -package nio -package examples - -import zio.blocking.Blocking -import zio.console.Console -import zio.nio.channels.{FileChannel, ManagedBlockingNioOps} -import zio.nio.charset.Charset -import zio.nio.file.Path -import zio.stream.ZStream - -/** - * Dumps a text file to the console using a specified encoding. - * - * Two command line parameters must be provided: - * 1. The path of the file to dump 2. The character encoding to use — optional, defaults to UTF-8 - */ -object TextFileDump extends App { - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { - val charset = (args match { - case _ :: s :: _ => Some(s) - case _ => None - }).flatMap(Charset.forNameIfSupported).getOrElse(Charset.Standard.utf8) - - val program = for { - fileArg <- ZIO.succeed(args.headOption).someOrFail(new Exception("File name must be specified")) - _ <- dump(charset, Path(fileArg)) - } yield () - - program.exitCode - } - - private def dump(charset: Charset, file: Path): ZIO[Console with Blocking, Exception, Unit] = - FileChannel.open(file).useNioBlockingOps { fileOps => - val inStream: ZStream[Blocking, Exception, Byte] = ZStream.repeatEffectChunkOption { - fileOps.readChunk(1000).asSomeError.flatMap { chunk => - if (chunk.isEmpty) ZIO.fail(None) else ZIO.succeed(chunk) - } - } - - // apply decoding transducer - val charStream: ZStream[Blocking, Exception, Char] = - inStream.transduce(charset.newDecoder.transducer()) - - charStream.foreachChunk(chars => console.putStr(chars.mkString)) - } - -} diff --git a/examples/src/main/scala/ToUppercaseAsAService.scala b/examples/src/main/scala/ToUppercaseAsAService.scala deleted file mode 100644 index ff15fee4..00000000 --- a/examples/src/main/scala/ToUppercaseAsAService.scala +++ /dev/null @@ -1,62 +0,0 @@ -package zio -package nio -package examples - -import zio.blocking.Blocking -import zio.clock.Clock -import zio.console.Console -import zio.nio.channels.{ManagedBlockingNioOps, ServerSocketChannel, SocketChannel} -import zio.nio.charset.Charset -import zio.stream.ZTransducer - -import java.io.IOException -import scala.util.control.Exception._ - -/** - * `toUpperCase` as a service. - * - * Using ZIO-NIO and ZIO streams to build a very silly TCP service. Listens on port 7777 by default. - * - * Send it UTF-8 text and it will send back the uppercase version. Amazing! - */ -object ToUppercaseAsAService extends App { - - private val upperCaseIfier = ZTransducer.identity[Char].map(_.toUpper) - - private def handleConnection(socket: SocketChannel): ZIO[Blocking with Console with Clock, IOException, Long] = { - - // this does the processing of the characters received over the channel via a transducer - // the stream of bytes from the channel is transduced, then written back to the same channel's sink - def transducer = - Charset.Standard.utf8.newDecoder.transducer() >>> - upperCaseIfier >>> - Charset.Standard.utf8.newEncoder.transducer() - console.putStrLn("Connection accepted") *> - socket.useBlocking { ops => - ops - .stream() - .transduce(transducer) - .run(ops.sink()) - .tapBoth( - e => console.putStrLn(s"Connection error: $e"), - i => console.putStrLn(s"Connection ended, wrote $i bytes") - ) - } - } - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { - val port = args.headOption - .flatMap(s => catching(classOf[IllegalArgumentException]).opt(s.toInt)) - .getOrElse(7777) - - ServerSocketChannel.open.useNioBlocking { (serverChannel, ops) => - InetSocketAddress.wildCard(port).flatMap { socketAddress => - serverChannel.bindTo(socketAddress) *> - console.putStrLn(s"Listening on $socketAddress") *> - ops.acceptAndFork(handleConnection).forever - } - }.exitCode - - } - -} diff --git a/nio/jvm/src/main/scala-2/zio/nio/channels/package.scala b/nio/jvm/src/main/scala-2/zio/nio/channels/package.scala new file mode 100644 index 00000000..cbd69dff --- /dev/null +++ b/nio/jvm/src/main/scala-2/zio/nio/channels/package.scala @@ -0,0 +1,40 @@ +package zio.nio + +import zio.{Trace, ZIO} + +import java.io.IOException + +package object channels { + + implicit final class BlockingNioOps[-R, +C <: BlockingChannel]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + + def flatMapNioBlocking[R1, E >: IOException, A]( + f: (C, C#BlockingOps) => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1 with Any, E, A] = + underlying.flatMap(c => c.flatMapBlocking(f(c, _))) + + def flatMapNioBlockingOps[R1, E >: IOException, A]( + f: C#BlockingOps => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1 with Any, E, A] = flatMapNioBlocking((_, ops) => f(ops)) + + } + + implicit final class NonBlockingNioOps[-R, +C <: SelectableChannel]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + + def flatMapNioNonBlocking[R1, E >: IOException, A](f: (C, C#NonBlockingOps) => ZIO[R1, E, A])(implicit + trace: Trace + ): ZIO[R with R1, E, A] = + underlying.flatMap(c => c.flatMapNonBlocking(f(c, _))) + + def flatMapNioNonBlockingOps[R1, E >: IOException, A](f: C#NonBlockingOps => ZIO[R1, E, A])(implicit + trace: Trace + ): ZIO[R with R1, E, A] = + flatMapNioNonBlocking((_, ops) => f(ops)) + + } + +} diff --git a/nio/jvm/src/main/scala-3/zio/nio/channels/package.scala b/nio/jvm/src/main/scala-3/zio/nio/channels/package.scala new file mode 100644 index 00000000..60eb948d --- /dev/null +++ b/nio/jvm/src/main/scala-3/zio/nio/channels/package.scala @@ -0,0 +1,41 @@ +package zio.nio + + +import zio.{ZIO, Trace} + +import java.io.IOException + +package object channels { + + implicit final class BlockingNioOps[-R, BO, C <: BlockingChannel { type BlockingOps <: BO }]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + type F1[R, E, A] = (C, BO) => ZIO[R, E, A] + + def flatMapNioBlocking[R1, E >: IOException, A]( + f: F1[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1, E, A] = underlying.flatMap(c => c.flatMapBlocking(f(c, _))) + + def flatMapNioBlockingOps[R1, E >: IOException, A]( + f: BO => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1, E, A] = flatMapNioBlocking((_, ops) => f(ops)) + + } + + implicit final class NonBlockingNioOps[-R, BO, C <: SelectableChannel { type NonBlockingOps <: BO }]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + + def flatMapNioNonBlocking[R1, E >: IOException, A](f: (C, BO) => ZIO[R1, E, A])(implicit + trace: Trace + ): ZIO[R with R1, E, A] = + underlying.flatMap(c => c.flatMapNonBlocking(f(c, _))) + + def flatMapNioNonBlockingOps[R1, E >: IOException, A](f: BO => ZIO[R1, E, A])(implicit + trace: Trace + ): ZIO[R with R1, E, A] = + flatMapNioNonBlocking((_, ops) => f(ops)) + + } + +} diff --git a/nio/jvm/src/main/scala/zio/nio/ZStreamHelper.scala b/nio/jvm/src/main/scala/zio/nio/ZStreamHelper.scala new file mode 100644 index 00000000..84e683f5 --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/ZStreamHelper.scala @@ -0,0 +1,47 @@ +package zio.nio + +import zio.stream.ZStream +import zio.{Scope, ZIO, Trace} + +/** + * A mutable buffer of shorts. + */ +object ZStreamHelper { + + /** + * Creates a stream from a Java stream + */ + final def fromJavaStream[A](stream: => java.util.stream.Stream[A])(implicit + trace: Trace + ): ZStream[Any, Throwable, A] = + fromJavaStream(stream, ZStream.DefaultChunkSize) + + /** + * Creates a stream from a Java stream + */ + final def fromJavaStream[A]( + stream: => java.util.stream.Stream[A], + chunkSize: Int + )(implicit trace: Trace): ZStream[Any, Throwable, A] = + ZStream.fromJavaIteratorScoped( + ZIO.acquireRelease(ZIO.attempt(stream))(stream => ZIO.succeed(stream.close())).map(_.iterator()), + chunkSize + ) + + /** + * Creates a stream from a scoped Java stream + */ + final def fromJavaStreamScoped[R, A](stream: => ZIO[Scope with R, Throwable, java.util.stream.Stream[A]])(implicit + trace: Trace + ): ZStream[R, Throwable, A] = + fromJavaStreamScoped[R, A](stream, ZStream.DefaultChunkSize) + + /** + * Creates a stream from a scoped Java stream + */ + final def fromJavaStreamScoped[R, A]( + stream: => ZIO[Scope with R, Throwable, java.util.stream.Stream[A]], + chunkSize: Int + )(implicit trace: Trace): ZStream[R, Throwable, A] = + ZStream.scoped[R](stream).flatMap(fromJavaStream(_, chunkSize)) +} diff --git a/nio/src/main/scala/zio/nio/channels/AsynchronousChannel.scala b/nio/jvm/src/main/scala/zio/nio/channels/AsynchronousChannel.scala similarity index 54% rename from nio/src/main/scala/zio/nio/channels/AsynchronousChannel.scala rename to nio/jvm/src/main/scala/zio/nio/channels/AsynchronousChannel.scala index d9f113f5..52431518 100644 --- a/nio/src/main/scala/zio/nio/channels/AsynchronousChannel.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/AsynchronousChannel.scala @@ -1,9 +1,7 @@ package zio.nio package channels - import zio._ -import zio.clock.Clock -import zio.duration._ + import zio.stream.{Stream, ZSink, ZStream} import java.io.{EOFException, IOException} @@ -33,11 +31,11 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: * * Fails with `java.io.EOFException` if end-of-stream is reached. */ - final def read(b: ByteBuffer): IO[IOException, Int] = + final def read(b: ByteBuffer)(implicit trace: Trace): IO[IOException, Int] = effectAsyncChannel[JAsynchronousByteChannel, JInteger](channel)(c => c.read(b.buffer, (), _)) .flatMap(eofCheck(_)) - final def readChunk(capacity: Int): IO[IOException, Chunk[Byte]] = + final def readChunk(capacity: Int)(implicit trace: Trace): IO[IOException, Chunk[Byte]] = for { b <- Buffer.byte(capacity) _ <- read(b) @@ -48,7 +46,7 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: /** * Writes data into this channel from buffer, returning the number of bytes written. */ - final def write(b: ByteBuffer): IO[IOException, Int] = + final def write(b: ByteBuffer)(implicit trace: Trace): IO[IOException, Int] = effectAsyncChannel[JAsynchronousByteChannel, JInteger](channel)(c => c.write(b.buffer, (), _)).map(_.toInt) /** @@ -56,12 +54,14 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: * * More than one write operation may be performed to write out the entire chunk. */ - final def writeChunk(chunk: Chunk[Byte]): ZIO[Clock, IOException, Unit] = + final def writeChunk(chunk: Chunk[Byte])(implicit trace: Trace): ZIO[Any, IOException, Unit] = for { b <- Buffer.byte(chunk) - _ <- write(b).repeatWhileM(_ => b.hasRemaining) + _ <- write(b).repeatWhileZIO(_ => b.hasRemaining) } yield () + def sink()(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = sink(Buffer.byte(5000)) + /** * A sink that will write all the bytes it receives to this channel. * @@ -69,20 +69,24 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: * Optional, overrides how to construct the buffer used to transfer bytes received by the sink to this channel. */ def sink( - bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000) - ): ZSink[Clock, IOException, Byte, Byte, Long] = - ZSink { + bufferConstruct0: => UIO[ByteBuffer] + )(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = + ZSink.fromPush { + val bufferConstruct = bufferConstruct0 for { - buffer <- bufferConstruct.toManaged_ - countRef <- Ref.makeManaged(0L) + buffer <- bufferConstruct + countRef <- Ref.make(0L) } yield (_: Option[Chunk[Byte]]).map { chunk => - def doWrite(total: Int, c: Chunk[Byte]): ZIO[Any with Clock, IOException, Int] = { + def doWrite(total: Int, c: Chunk[Byte])(implicit + trace: Trace + ): ZIO[Any, IOException, Int] = { val x = for { remaining <- buffer.putChunk(c) _ <- buffer.flip - count <- ZStream - .repeatEffectWith(write(buffer), Schedule.recurWhileM(Function.const(buffer.hasRemaining))) - .runSum + count <- + ZStream + .repeatZIOWithSchedule(write(buffer), Schedule.recurWhileZIO(Function.const(buffer.hasRemaining))) + .runSum _ <- buffer.clear } yield (count + total, remaining) x.flatMap { @@ -91,18 +95,20 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: } } - doWrite(0, chunk).foldM( + doWrite(0, chunk).foldZIO( e => buffer.getChunk().flatMap(c => ZIO.fail((Left(e), c))), count => countRef.update(_ + count.toLong) ) } .getOrElse( - countRef.get.flatMap[Clock, (Either[IOException, Long], Chunk[Byte]), Unit](count => + countRef.get.flatMap[Any, (Either[IOException, Long], Chunk[Byte]), Unit](count => ZIO.fail((Right(count), Chunk.empty)) ) ) } + def stream()(implicit trace: Trace): Stream[IOException, Byte] = stream(Buffer.byte(5000)) + /** * A `ZStream` that reads from this channel. The stream terminates without error if the channel reaches end-of-stream. * @@ -110,19 +116,21 @@ abstract class AsynchronousByteChannel private[channels] (protected val channel: * Optional, overrides how to construct the buffer used to transfer bytes read from this channel into the stream. */ def stream( - bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000) - ): Stream[IOException, Byte] = - ZStream { - bufferConstruct.toManaged_.map { buffer => + bufferConstruct: UIO[ByteBuffer] + )(implicit trace: Trace): Stream[IOException, Byte] = + ZStream.unwrapScoped { + bufferConstruct.map { buffer => val doRead = for { _ <- read(buffer) _ <- buffer.flip chunk <- buffer.getChunk() _ <- buffer.clear } yield chunk - doRead.mapError { - case _: EOFException => None - case e => Some(e) + ZStream.repeatZIOChunkOption { + doRead.mapError { + case _: EOFException => None + case e => Some(e) + } } } } @@ -138,63 +146,69 @@ object AsynchronousByteChannel { */ private[channels] def effectAsyncChannel[C <: JChannel, A]( channel: C - )(op: C => CompletionHandler[A, Any] => Any): IO[IOException, A] = - Task(op(channel)) - .flatMap(Task.effectAsyncWithCompletionHandler) + )(op: C => CompletionHandler[A, Any] => Any)(implicit trace: Trace): IO[IOException, A] = + ZIO + .attempt(op(channel)) + .flatMap(ZIO.asyncWithCompletionHandler _) .refineToOrDie[IOException] - .onInterrupt(IO.effect(channel.close()).ignore) + .onInterrupt(ZIO.attempt(channel.close()).ignore) } final class AsynchronousServerSocketChannel(protected val channel: JAsynchronousServerSocketChannel) extends Channel { - def bindTo(local: SocketAddress, backlog: Int = 0): IO[IOException, Unit] = bind(Some(local), backlog) + def bindTo(local: SocketAddress, backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + bind(Some(local), backlog) - def bindAuto(backlog: Int = 0): IO[IOException, Unit] = bind(None, backlog) + def bindAuto(backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = bind(None, backlog) /** * Binds the channel's socket to a local address and configures the socket to listen for connections, up to backlog * pending connection. */ - def bind(address: Option[SocketAddress], backlog: Int = 0): IO[IOException, Unit] = - IO.effect(channel.bind(address.map(_.jSocketAddress).orNull, backlog)).refineToOrDie[IOException].unit + def bind(address: Option[SocketAddress], backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(address.map(_.jSocketAddress).orNull, backlog)).refineToOrDie[IOException].unit - def setOption[T](name: SocketOption[T], value: T): IO[IOException, Unit] = - IO.effect(channel.setOption(name, value)).refineToOrDie[IOException].unit + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit /** * Accepts a connection. */ - def accept: Managed[IOException, AsynchronousSocketChannel] = + def accept(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousSocketChannel] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousServerSocketChannel, JAsynchronousSocketChannel](channel)(c => c.accept((), _)) .map(AsynchronousSocketChannel.fromJava) - .toNioManaged + .toNioScoped /** * The `SocketAddress` that the socket is bound to, or the `SocketAddress` representing the loopback address if denied * by the security manager, or `Maybe.empty` if the channel's socket is not bound. */ - def localAddress: IO[IOException, Option[SocketAddress]] = - IO.effect( - Option(channel.getLocalAddress).map(SocketAddress.fromJava) - ).refineToOrDie[IOException] + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getLocalAddress).map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] } object AsynchronousServerSocketChannel { - def open: Managed[IOException, AsynchronousServerSocketChannel] = - IO.effect(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open())) + def open(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousServerSocketChannel] = + ZIO + .attempt(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open())) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped def open( channelGroup: AsynchronousChannelGroup - ): Managed[IOException, AsynchronousServerSocketChannel] = - IO.effect(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open(channelGroup.channelGroup))) + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousServerSocketChannel] = + ZIO + .attempt(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open(channelGroup.channelGroup))) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped def fromJava(channel: JAsynchronousServerSocketChannel): AsynchronousServerSocketChannel = new AsynchronousServerSocketChannel(channel) @@ -204,33 +218,39 @@ object AsynchronousServerSocketChannel { final class AsynchronousSocketChannel(override protected val channel: JAsynchronousSocketChannel) extends AsynchronousByteChannel(channel) { - def bindTo(address: SocketAddress): IO[IOException, Unit] = bind(Some(address)) + def bindTo(address: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = bind(Some(address)) - def bindAuto: IO[IOException, Unit] = bind(None) + def bindAuto(implicit trace: Trace): IO[IOException, Unit] = bind(None) - def bind(address: Option[SocketAddress]): IO[IOException, Unit] = - IO.effect(channel.bind(address.map(_.jSocketAddress).orNull)).refineToOrDie[IOException].unit + def bind(address: Option[SocketAddress])(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(address.map(_.jSocketAddress).orNull)).refineToOrDie[IOException].unit - def setOption[T](name: SocketOption[T], value: T): IO[IOException, Unit] = - IO.effect(channel.setOption(name, value)).refineToOrDie[IOException].unit + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit - def shutdownInput: IO[IOException, Unit] = IO.effect(channel.shutdownInput()).refineToOrDie[IOException].unit + def shutdownInput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownInput()).refineToOrDie[IOException].unit - def shutdownOutput: IO[IOException, Unit] = IO.effect(channel.shutdownOutput()).refineToOrDie[IOException].unit + def shutdownOutput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownOutput()).refineToOrDie[IOException].unit - def remoteAddress: IO[IOException, Option[SocketAddress]] = - IO.effect( - Option(channel.getRemoteAddress) - .map(SocketAddress.fromJava) - ).refineToOrDie[IOException] + def remoteAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getRemoteAddress) + .map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] - def localAddress: IO[IOException, Option[SocketAddress]] = - IO.effect( - Option(channel.getLocalAddress) - .map(SocketAddress.fromJava) - ).refineToOrDie[IOException] + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getLocalAddress) + .map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] - def connect(socketAddress: SocketAddress): IO[IOException, Unit] = + def connect(socketAddress: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousSocketChannel, JVoid](channel)(c => c.connect(socketAddress.jSocketAddress, (), _) @@ -242,7 +262,7 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron * * Fails with `java.io.EOFException` if end-of-stream is reached. */ - def read(dst: ByteBuffer, timeout: Duration): IO[IOException, Int] = + def read(dst: ByteBuffer, timeout: Duration)(implicit trace: Trace): IO[IOException, Int] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousSocketChannel, JInteger](channel) { channel => channel.read( @@ -255,7 +275,7 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron } .flatMap(eofCheck(_)) - def readChunk(capacity: Int, timeout: Duration): IO[IOException, Chunk[Byte]] = + def readChunk(capacity: Int, timeout: Duration)(implicit trace: Trace): IO[IOException, Chunk[Byte]] = for { b <- Buffer.byte(capacity) _ <- read(b, timeout) @@ -271,7 +291,7 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron def read( dsts: List[ByteBuffer], timeout: Duration - ): IO[IOException, Long] = + )(implicit trace: Trace): IO[IOException, Long] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousSocketChannel, JLong](channel) { channel => val a = dsts.map(_.buffer).toArray @@ -290,19 +310,19 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron def readChunks( capacities: List[Int], timeout: Duration - ): IO[IOException, List[Chunk[Byte]]] = - IO.foreach(capacities)(Buffer.byte).flatMap { buffers => - read(buffers, timeout) *> IO.foreach(buffers)(b => b.flip *> b.getChunk()) + )(implicit trace: Trace): IO[IOException, List[Chunk[Byte]]] = + ZIO.foreach(capacities)(Buffer.byte).flatMap { buffers => + read(buffers, timeout) *> ZIO.foreach(buffers)(b => b.flip *> b.getChunk()) } - def write(src: ByteBuffer, timeout: Duration): IO[IOException, Int] = + def write(src: ByteBuffer, timeout: Duration)(implicit trace: Trace): IO[IOException, Int] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousSocketChannel, JInteger](channel) { channel => channel.write(src.buffer, timeout.fold(Long.MaxValue, _.toNanos), TimeUnit.NANOSECONDS, (), _) } .map(_.toInt) - def writeChunk(chunk: Chunk[Byte], timeout: Duration): IO[IOException, Unit] = + def writeChunk(chunk: Chunk[Byte], timeout: Duration)(implicit trace: Trace): IO[IOException, Unit] = for { b <- Buffer.byte(chunk.length) _ <- b.putChunk(chunk) @@ -313,7 +333,7 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron def write( srcs: List[ByteBuffer], timeout: Duration - ): IO[IOException, Long] = + )(implicit trace: Trace): IO[IOException, Long] = AsynchronousByteChannel .effectAsyncChannel[JAsynchronousSocketChannel, JLong](channel) { channel => val a = srcs.map(_.buffer).toArray @@ -329,24 +349,30 @@ final class AsynchronousSocketChannel(override protected val channel: JAsynchron } .map(_.toLong) - def writeChunks(chunks: List[Chunk[Byte]], timeout: Duration): IO[IOException, Long] = - IO.foreach(chunks) { chunk => - Buffer.byte(chunk.length).tap(_.putChunk(chunk)).tap(_.flip) - }.flatMap(write(_, timeout)) + def writeChunks(chunks: List[Chunk[Byte]], timeout: Duration)(implicit trace: Trace): IO[IOException, Long] = + ZIO + .foreach(chunks) { chunk => + Buffer.byte(chunk.length).tap(_.putChunk(chunk)).tap(_.flip) + } + .flatMap(write(_, timeout)) } object AsynchronousSocketChannel { - def open: Managed[IOException, AsynchronousSocketChannel] = - IO.effect(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open())) + def open(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousSocketChannel] = + ZIO + .attempt(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open())) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped - def open(channelGroup: AsynchronousChannelGroup): Managed[IOException, AsynchronousSocketChannel] = - IO.effect(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open(channelGroup.channelGroup))) + def open( + channelGroup: AsynchronousChannelGroup + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousSocketChannel] = + ZIO + .attempt(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open(channelGroup.channelGroup))) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped def fromJava(asyncSocketChannel: JAsynchronousSocketChannel): AsynchronousSocketChannel = new AsynchronousSocketChannel(asyncSocketChannel) diff --git a/nio/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala b/nio/jvm/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala similarity index 63% rename from nio/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala rename to nio/jvm/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala index 0a7d6dee..cce0bc97 100644 --- a/nio/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala @@ -1,9 +1,8 @@ package zio.nio package channels - import zio._ -import zio.clock.Clock import zio.nio.file.Path + import zio.stream.{Stream, ZSink, ZStream} import java.io.{EOFException, IOException} @@ -17,9 +16,12 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan import AsynchronousByteChannel.effectAsyncChannel - def force(metaData: Boolean): IO[IOException, Unit] = IO.effect(channel.force(metaData)).refineToOrDie[IOException] + def force(metaData: Boolean)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.force(metaData)).refineToOrDie[IOException] - def lock(position: Long = 0L, size: Long = Long.MaxValue, shared: Boolean = false): IO[IOException, FileLock] = + def lock(position: Long = 0L, size: Long = Long.MaxValue, shared: Boolean = false)(implicit + trace: Trace + ): IO[IOException, FileLock] = effectAsyncChannel[JAsynchronousFileChannel, JFileLock](channel)(c => c.lock(position, size, shared, (), _)) .map(new FileLock(_)) @@ -31,7 +33,7 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan * @param position * The file position at which the transfer is to begin; must be non-negative */ - def read(dst: ByteBuffer, position: Long): IO[IOException, Int] = + def read(dst: ByteBuffer, position: Long)(implicit trace: Trace): IO[IOException, Int] = dst.withJavaBuffer { buf => effectAsyncChannel[JAsynchronousFileChannel, Integer](channel)(c => c.read(buf, position, (), _)) .flatMap(eofCheck(_)) @@ -45,7 +47,7 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan * @param position * The file position at which the transfer is to begin; must be non-negative */ - def readChunk(capacity: Int, position: Long): IO[IOException, Chunk[Byte]] = + def readChunk(capacity: Int, position: Long)(implicit trace: Trace): IO[IOException, Chunk[Byte]] = for { b <- Buffer.byte(capacity) _ <- read(b, position) @@ -53,18 +55,19 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan chunk <- b.getChunk() } yield chunk - def size: IO[IOException, Long] = IO.effect(channel.size()).refineToOrDie[IOException] + def size(implicit trace: Trace): IO[IOException, Long] = ZIO.attempt(channel.size()).refineToOrDie[IOException] - def truncate(size: Long): IO[IOException, Unit] = IO.effect(channel.truncate(size)).refineToOrDie[IOException].unit + def truncate(size: Long)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.truncate(size)).refineToOrDie[IOException].unit def tryLock( position: Long = 0L, size: Long = Long.MaxValue, shared: Boolean = false - ): IO[IOException, FileLock] = - IO.effect(new FileLock(channel.tryLock(position, size, shared))).refineToOrDie[IOException] + )(implicit trace: Trace): IO[IOException, FileLock] = + ZIO.attempt(new FileLock(channel.tryLock(position, size, shared))).refineToOrDie[IOException] - def write(src: ByteBuffer, position: Long): IO[IOException, Int] = + def write(src: ByteBuffer, position: Long)(implicit trace: Trace): IO[IOException, Int] = src.withJavaBuffer { buf => effectAsyncChannel[JAsynchronousFileChannel, Integer](channel)(c => c.write(buf, position, (), _)) .map(_.intValue) @@ -80,18 +83,21 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan * @param position * Where in the file to write. */ - def writeChunk(src: Chunk[Byte], position: Long): IO[IOException, Unit] = + def writeChunk(src: Chunk[Byte], position: Long)(implicit trace: Trace): IO[IOException, Unit] = Buffer.byte(src).flatMap { b => - def go(pos: Long): IO[IOException, Unit] = + def go(pos: Long)(implicit trace: Trace): IO[IOException, Unit] = write(b, pos).flatMap { bytesWritten => b.hasRemaining.flatMap { case true => go(pos + bytesWritten.toLong) - case false => IO.unit + case false => ZIO.unit } } go(position) } + def stream(position: Long)(implicit trace: Trace): Stream[IOException, Byte] = + stream(position, Buffer.byte(5000)) + /** * A `ZStream` that reads from this channel. * @@ -103,11 +109,13 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan * Optional, overrides how to construct the buffer used to transfer bytes read from this channel into the stream. By * default a heap buffer is used, but a direct buffer will usually perform better. */ - def stream(position: Long, bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000)): Stream[IOException, Byte] = - ZStream { + def stream(position: Long, bufferConstruct: UIO[ByteBuffer])(implicit + trace: Trace + ): Stream[IOException, Byte] = + ZStream.unwrapScoped { for { - posRef <- Ref.makeManaged(position) - buffer <- bufferConstruct.toManaged_ + posRef <- Ref.make(position) + buffer <- bufferConstruct } yield { val doRead = for { pos <- posRef.get @@ -117,13 +125,16 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan chunk <- buffer.getChunk() _ <- buffer.clear } yield chunk - doRead.mapError { + ZStream.repeatZIOChunkOption(doRead.mapError { case _: EOFException => None case e => Some(e) - } + }) } } + def sink(position: Long)(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = + sink(position, Buffer.byte(5000)) + /** * A sink that will write all the bytes it receives to this channel. The sink's result is the number of bytes written. * @@ -135,21 +146,21 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan */ def sink( position: Long, - bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000) - ): ZSink[Clock, IOException, Byte, Byte, Long] = - ZSink { + bufferConstruct: UIO[ByteBuffer] + )(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = + ZSink.fromPush { for { - buffer <- bufferConstruct.toManaged_ - posRef <- Ref.makeManaged(position) + buffer <- bufferConstruct + posRef <- Ref.make(position) } yield (_: Option[Chunk[Byte]]).map { chunk => - def doWrite(currentPos: Long, c: Chunk[Byte]): ZIO[Clock, IOException, Long] = { + def doWrite(currentPos: Long, c: Chunk[Byte])(implicit trace: Trace): ZIO[Any, IOException, Long] = { val x = for { remaining <- buffer.putChunk(c) _ <- buffer.flip count <- ZStream - .repeatEffectWith( + .repeatZIOWithSchedule( write(buffer, currentPos), - Schedule.recurWhileM(Function.const(buffer.hasRemaining)) + Schedule.recurWhileZIO(Function.const(buffer.hasRemaining)) ) .runSum _ <- buffer.clear @@ -163,14 +174,15 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan for { currentPos <- posRef.get - newPos <- doWrite(currentPos, chunk).catchAll(e => buffer.getChunk().flatMap(ZSink.Push.fail(e, _))) - _ <- posRef.set(newPos) + newPos <- + doWrite(currentPos, chunk).catchAll(e => buffer.getChunk().flatMap(chunk => ZIO.fail((Left(e), chunk)))) + _ <- posRef.set(newPos) } yield () } .getOrElse( posRef.get.flatMap[Any, (Either[IOException, Long], Chunk[Byte]), Unit](finalPos => - ZSink.Push.emit(finalPos - position, Chunk.empty) + ZIO.fail((Right(finalPos - position), Chunk.empty)) ) ) } @@ -179,23 +191,28 @@ final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChan object AsynchronousFileChannel { - def open(file: Path, options: OpenOption*): Managed[IOException, AsynchronousFileChannel] = - IO.effect(new AsynchronousFileChannel(JAsynchronousFileChannel.open(file.javaPath, options: _*))) + def open(file: Path, options: OpenOption*)(implicit + trace: Trace + ): ZIO[Scope, IOException, AsynchronousFileChannel] = + ZIO + .attempt(new AsynchronousFileChannel(JAsynchronousFileChannel.open(file.javaPath, options: _*))) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped def open( file: Path, options: Set[OpenOption], executor: Option[ExecutionContextExecutorService], attrs: Set[FileAttribute[_]] - ): Managed[IOException, AsynchronousFileChannel] = - IO.effect( - new AsynchronousFileChannel( - JAsynchronousFileChannel.open(file.javaPath, options.asJava, executor.orNull, attrs.toSeq: _*) + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousFileChannel] = + ZIO + .attempt( + new AsynchronousFileChannel( + JAsynchronousFileChannel.open(file.javaPath, options.asJava, executor.orNull, attrs.toSeq: _*) + ) ) - ).refineToOrDie[IOException] - .toNioManaged + .refineToOrDie[IOException] + .toNioScoped def fromJava(javaAsynchronousFileChannel: JAsynchronousFileChannel): AsynchronousFileChannel = new AsynchronousFileChannel(javaAsynchronousFileChannel) diff --git a/nio/src/main/scala/zio/nio/channels/DatagramChannel.scala b/nio/jvm/src/main/scala/zio/nio/channels/DatagramChannel.scala similarity index 60% rename from nio/src/main/scala/zio/nio/channels/DatagramChannel.scala rename to nio/jvm/src/main/scala/zio/nio/channels/DatagramChannel.scala index 59197b45..2bfd8ccb 100644 --- a/nio/src/main/scala/zio/nio/channels/DatagramChannel.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/DatagramChannel.scala @@ -1,7 +1,7 @@ package zio.nio package channels -import zio.{IO, Managed, UIO} +import zio.{IO, Scope, Trace, UIO, ZIO} import java.io.IOException import java.net.{DatagramSocket => JDatagramSocket, ProtocolFamily, SocketAddress => JSocketAddress, SocketOption} @@ -29,8 +29,8 @@ final class DatagramChannel private[channels] (override protected val channel: J * @param remote * the remote address */ - def connect(remote: SocketAddress): IO[IOException, Unit] = - IO.effect(new DatagramChannel(self.channel.connect(remote.jSocketAddress))).unit.refineToOrDie[IOException] + def connect(remote: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(new DatagramChannel(self.channel.connect(remote.jSocketAddress))).unit.refineToOrDie[IOException] /** * Sends a datagram via this channel to the given target [[zio.nio.SocketAddress]]. @@ -42,8 +42,8 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the number of bytes that were sent over this channel */ - def send(src: ByteBuffer, target: SocketAddress): IO[IOException, Int] = - IO.effect(self.channel.send(src.buffer, target.jSocketAddress)).refineToOrDie[IOException] + def send(src: ByteBuffer, target: SocketAddress)(implicit trace: Trace): IO[IOException, Int] = + ZIO.attempt(self.channel.send(src.buffer, target.jSocketAddress)).refineToOrDie[IOException] } @@ -57,8 +57,9 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the socket address of the datagram's source, if available. */ - def receive(dst: ByteBuffer): IO[IOException, SocketAddress] = - IO.effect(SocketAddress.fromJava(self.channel.receive(dst.buffer))) + def receive(dst: ByteBuffer)(implicit trace: Trace): IO[IOException, SocketAddress] = + ZIO + .attempt(SocketAddress.fromJava(self.channel.receive(dst.buffer))) .refineToOrDie[IOException] } @@ -75,16 +76,16 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the socket address of the datagram's source, if available. */ - def receive(dst: ByteBuffer): IO[IOException, Option[SocketAddress]] = - IO.effect(Option(self.channel.receive(dst.buffer)).map(SocketAddress.fromJava)).refineToOrDie[IOException] + def receive(dst: ByteBuffer)(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO.attempt(Option(self.channel.receive(dst.buffer)).map(SocketAddress.fromJava)).refineToOrDie[IOException] } override protected def makeNonBlockingOps: NonBlockingDatagramOps = new NonBlockingDatagramOps - def bindTo(local: SocketAddress): IO[IOException, Unit] = bind(Some(local)) + def bindTo(local: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = bind(Some(local)) - def bindAuto: IO[IOException, Unit] = bind(None) + def bindAuto(implicit trace: Trace): IO[IOException, Unit] = bind(None) /** * Binds this channel's underlying socket to the given local address. Passing `None` binds to an automatically @@ -95,16 +96,16 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the datagram channel bound to the local address */ - def bind(local: Option[SocketAddress]): IO[IOException, Unit] = { + def bind(local: Option[SocketAddress])(implicit trace: Trace): IO[IOException, Unit] = { val addr: JSocketAddress = local.map(_.jSocketAddress).orNull - IO.effect(self.channel.bind(addr)).refineToOrDie[IOException].unit + ZIO.attempt(self.channel.bind(addr)).refineToOrDie[IOException].unit } /** * Disconnects this channel's underlying socket. */ - def disconnect: IO[IOException, Unit] = - IO.effect(new DatagramChannel(self.channel.disconnect())).unit.refineToOrDie[IOException] + def disconnect(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(new DatagramChannel(self.channel.disconnect())).unit.refineToOrDie[IOException] /** * Tells whether this channel's underlying socket is both open and connected. @@ -112,7 +113,7 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * `true` when the socket is both open and connected, otherwise `false` */ - def isConnected: UIO[Boolean] = UIO.effectTotal(self.channel.isConnected()) + def isConnected(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(self.channel.isConnected()) /** * Optionally returns the socket address that this channel's underlying socket is bound to. @@ -120,8 +121,8 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the local address if the socket is bound, otherwise `None` */ - def localAddress: IO[IOException, Option[SocketAddress]] = - IO.effect(Option(self.channel.getLocalAddress()).map(SocketAddress.fromJava)).refineToOrDie[IOException] + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO.attempt(Option(self.channel.getLocalAddress()).map(SocketAddress.fromJava)).refineToOrDie[IOException] /** * Optionally returns the remote socket address that this channel's underlying socket is connected to. @@ -129,8 +130,8 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the remote address if the socket is connected, otherwise `None` */ - def remoteAddress: IO[IOException, Option[SocketAddress]] = - IO.effect(Option(self.channel.getRemoteAddress()).map(SocketAddress.fromJava)).refineToOrDie[IOException] + def remoteAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO.attempt(Option(self.channel.getRemoteAddress()).map(SocketAddress.fromJava)).refineToOrDie[IOException] /** * Sets the value of the given socket option. @@ -140,8 +141,8 @@ final class DatagramChannel private[channels] (override protected val channel: J * @param value * the value to be set */ - def setOption[T](name: SocketOption[T], value: T): IO[IOException, Unit] = - IO.effect(self.channel.setOption(name, value)).refineToOrDie[IOException].unit + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(self.channel.setOption(name, value)).refineToOrDie[IOException].unit /** * Returns a reference to this channel's underlying datagram socket. @@ -149,7 +150,7 @@ final class DatagramChannel private[channels] (override protected val channel: J * @return * the underlying datagram socket */ - def socket: UIO[JDatagramSocket] = IO.effectTotal(self.channel.socket()) + def socket(implicit trace: Trace): UIO[JDatagramSocket] = ZIO.succeed(self.channel.socket()) } @@ -161,17 +162,18 @@ object DatagramChannel { * @return * a new datagram channel */ - def open: Managed[IOException, DatagramChannel] = - IO.effect(new DatagramChannel(JDatagramChannel.open())) + def open(implicit trace: Trace): ZIO[Scope, IOException, DatagramChannel] = + ZIO + .attempt(new DatagramChannel(JDatagramChannel.open())) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped - def open(family: ProtocolFamily): Managed[IOException, DatagramChannel] = - IO.effect { + def open(family: ProtocolFamily)(implicit trace: Trace): ZIO[Scope, IOException, DatagramChannel] = + ZIO.attempt { val javaChannel = JDatagramChannel.open(family) javaChannel.configureBlocking(false) fromJava(javaChannel) - }.refineToOrDie[IOException].toNioManaged + }.refineToOrDie[IOException].toNioScoped def fromJava(javaDatagramChannel: JDatagramChannel): DatagramChannel = new DatagramChannel(javaDatagramChannel) diff --git a/nio/src/main/scala/zio/nio/channels/Pipe.scala b/nio/jvm/src/main/scala/zio/nio/channels/Pipe.scala similarity index 64% rename from nio/src/main/scala/zio/nio/channels/Pipe.scala rename to nio/jvm/src/main/scala/zio/nio/channels/Pipe.scala index 76993358..0efa54cc 100644 --- a/nio/src/main/scala/zio/nio/channels/Pipe.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/Pipe.scala @@ -1,18 +1,18 @@ package zio.nio package channels -import zio.{IO, Managed} +import zio.{IO, Scope, Trace, ZIO} import java.io.IOException import java.nio.channels.{Pipe => JPipe} -final class Pipe private (private val pipe: JPipe) { +final class Pipe private (private val pipe: JPipe)(implicit trace: Trace) { - val source: Managed[Nothing, Pipe.SourceChannel] = - IO.effectTotal(new channels.Pipe.SourceChannel(pipe.source())).toNioManaged + def source(implicit trace: Trace): ZIO[Scope, Nothing, Pipe.SourceChannel] = + ZIO.succeed(new channels.Pipe.SourceChannel(pipe.source())).toNioScoped - val sink: Managed[Nothing, Pipe.SinkChannel] = - IO.effectTotal(new Pipe.SinkChannel(pipe.sink())).toNioManaged + def sink(implicit trace: Trace): ZIO[Scope, Nothing, Pipe.SinkChannel] = + ZIO.succeed(new Pipe.SinkChannel(pipe.sink())).toNioScoped } @@ -54,9 +54,9 @@ object Pipe { } - val open: IO[IOException, Pipe] = - IO.effect(new Pipe(JPipe.open())).refineToOrDie[IOException] + def open(implicit trace: Trace): IO[IOException, Pipe] = + ZIO.attempt(new Pipe(JPipe.open())).refineToOrDie[IOException] - def fromJava(javaPipe: JPipe): Pipe = new Pipe(javaPipe) + def fromJava(javaPipe: JPipe)(implicit trace: Trace): Pipe = new Pipe(javaPipe) } diff --git a/nio/jvm/src/main/scala/zio/nio/channels/SelectableChannel.scala b/nio/jvm/src/main/scala/zio/nio/channels/SelectableChannel.scala new file mode 100644 index 00000000..f23b354d --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/channels/SelectableChannel.scala @@ -0,0 +1,262 @@ +package zio.nio +package channels + +import zio.nio.channels.SelectionKey.Operation +import zio.nio.channels.spi.SelectorProvider + +import zio.{Exit, Fiber, IO, Scope, Trace, UIO, ZIO} + +import java.io.IOException +import java.net.{ServerSocket => JServerSocket, Socket => JSocket, SocketOption} +import java.nio.channels.{ + ClosedChannelException, + SelectableChannel => JSelectableChannel, + ServerSocketChannel => JServerSocketChannel, + SocketChannel => JSocketChannel +} + +/** + * A channel that can be multiplexed via a [[zio.nio.channels.Selector]]. + */ +trait SelectableChannel extends BlockingChannel { + + /** + * The non-blocking operations supported by this channel. + */ + type NonBlockingOps + + protected val channel: JSelectableChannel + + final def provider(implicit trace: Trace): UIO[SelectorProvider] = + ZIO.succeed(new SelectorProvider(channel.provider())) + + final def validOps(implicit trace: Trace): UIO[Set[Operation]] = + ZIO + .succeed(channel.validOps()) + .map(Operation.fromInt(_)) + + final def isRegistered(implicit trace: Trace): UIO[Boolean] = + ZIO.succeed(channel.isRegistered()) + + final def keyFor(sel: Selector)(implicit trace: Trace): UIO[Option[SelectionKey]] = + ZIO.succeed(Option(channel.keyFor(sel.selector)).map(new SelectionKey(_))) + + /** + * Registers this channel with the given selector, returning a selection key. + * + * @param selector + * The selector to register with. + * @param ops + * The key's interest set will be created with these operations. + * @param attachment + * The object to attach to the key, if any. + * @return + * The new `SelectionKey`. + */ + final def register( + selector: Selector, + ops: Set[Operation] = Set.empty, + attachment: Option[AnyRef] = None + )(implicit trace: Trace): IO[ClosedChannelException, SelectionKey] = + ZIO + .attempt(new SelectionKey(channel.register(selector.selector, Operation.toInt(ops), attachment.orNull))) + .refineToOrDie[ClosedChannelException] + + final def configureBlocking(block: Boolean)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.configureBlocking(block)).unit.refineToOrDie[IOException] + + final def isBlocking(implicit trace: Trace): UIO[Boolean] = + ZIO.succeed(channel.isBlocking()) + + final def blockingLock(implicit trace: Trace): UIO[AnyRef] = + ZIO.succeed(channel.blockingLock()) + + protected def makeBlockingOps: BlockingOps + + final override def flatMapBlocking[R, E >: IOException, A](f: BlockingOps => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R with Any, E, A] = + configureBlocking(true) *> nioBlocking(f(makeBlockingOps)) + + protected def makeNonBlockingOps: NonBlockingOps + + /** + * Puts this channel into non-blocking mode and performs a set of non-blocking operations. + * + * @param f + * Uses the `NonBlockingOps` appropriate for this channel type to produce non-blocking effects. + */ + final def flatMapNonBlocking[R, E >: IOException, A](f: NonBlockingOps => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R, E, A] = + configureBlocking(false) *> f(makeNonBlockingOps) + +} + +final class SocketChannel(override protected[channels] val channel: JSocketChannel) extends SelectableChannel { + + self => + + override type BlockingOps = BlockingSocketOps + + override type NonBlockingOps = NonBlockingSocketOps + + sealed abstract class Ops extends GatheringByteOps with ScatteringByteOps { + override protected[channels] def channel = self.channel + } + + final class BlockingSocketOps private[SocketChannel] () extends Ops { + + def connect(remote: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(self.channel.connect(remote.jSocketAddress)).refineToOrDie[IOException].unit + + } + + override protected def makeBlockingOps = new BlockingSocketOps + + final class NonBlockingSocketOps private[SocketChannel] () extends Ops { + + def isConnectionPending(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(self.channel.isConnectionPending) + + def connect(remote: SocketAddress)(implicit trace: Trace): IO[IOException, Boolean] = + ZIO.attempt(self.channel.connect(remote.jSocketAddress)).refineToOrDie[IOException] + + def finishConnect(implicit trace: Trace): IO[IOException, Boolean] = + ZIO.attempt(self.channel.finishConnect()).refineToOrDie[IOException] + + } + + override protected def makeNonBlockingOps = new NonBlockingSocketOps + + def bindTo(address: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = bind(Some(address)) + + def bindAuto(implicit trace: Trace): IO[IOException, Unit] = bind(None) + + def bind(local: Option[SocketAddress])(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(local.map(_.jSocketAddress).orNull)).refineToOrDie[IOException].unit + + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit + + def shutdownInput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownInput()).refineToOrDie[IOException].unit + + def shutdownOutput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownOutput()).refineToOrDie[IOException].unit + + def socket(implicit trace: Trace): UIO[JSocket] = ZIO.succeed(channel.socket()) + + def isConnected(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(channel.isConnected) + + def remoteAddress(implicit trace: Trace): IO[IOException, SocketAddress] = + ZIO.attempt(SocketAddress.fromJava(channel.getRemoteAddress())).refineToOrDie[IOException] + + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt(Option(channel.getLocalAddress()).map(SocketAddress.fromJava)) + .refineToOrDie[IOException] + +} + +object SocketChannel { + + def fromJava(javaSocketChannel: JSocketChannel): SocketChannel = new SocketChannel(javaSocketChannel) + + def open(implicit trace: Trace): ZIO[Scope, IOException, SocketChannel] = + ZIO.attempt(new SocketChannel(JSocketChannel.open())).refineToOrDie[IOException].toNioScoped + + def open(remote: SocketAddress)(implicit trace: Trace): ZIO[Scope, IOException, SocketChannel] = + ZIO.attempt(new SocketChannel(JSocketChannel.open(remote.jSocketAddress))).refineToOrDie[IOException].toNioScoped + +} + +final class ServerSocketChannel(override protected val channel: JServerSocketChannel) extends SelectableChannel { + + override type BlockingOps = BlockingServerSocketOps + + override type NonBlockingOps = NonBlockingServerSocketOps + + final class BlockingServerSocketOps private[ServerSocketChannel] () { + + /** + * Accepts a socket connection. + * + * @return + * The channel for the accepted socket connection. + */ + def accept(implicit trace: Trace): ZIO[Scope, IOException, SocketChannel] = + ZIO.attempt(new SocketChannel(channel.accept())).refineToOrDie[IOException].toNioScoped + + /** + * Accepts a connection and uses it to perform an effect on a forked fiber. + * + * @param use + * Uses the accepted socket channel to produce an effect value, which will be run on a forked fiber. + * @return + * The fiber running the effect. + */ + def acceptAndFork[R, A]( + use: SocketChannel => ZIO[R, IOException, A] + )(implicit trace: Trace): ZIO[R, IOException, Fiber[IOException, A]] = + ZIO.uninterruptibleMask { restore => + Scope.make.flatMap { scope => + scope + .extend(restore(accept)) + .foldCauseZIO( + cause => scope.close(Exit.failCause(cause)) *> ZIO.failCause(cause), + socketChannel => scope.use[R](restore(use(socketChannel))).fork + ) + } + } + } + + override protected def makeBlockingOps: BlockingServerSocketOps = new BlockingServerSocketOps + + final class NonBlockingServerSocketOps private[ServerSocketChannel] () { + + /** + * Accepts a socket connection. + * + * @return + * None if no connection is currently available to be accepted. + */ + def accept(implicit trace: Trace): ZIO[Scope, IOException, Option[SocketChannel]] = + ZIO.acquireRelease { + ZIO + .attempt(Option(channel.accept()).map(new SocketChannel(_))) + .refineToOrDie[IOException] + } { + ZIO.whenCase(_) { case Some(channel) => + channel.close.ignore + } + } + } + + override protected def makeNonBlockingOps: NonBlockingServerSocketOps = new NonBlockingServerSocketOps + + def bindTo(local: SocketAddress, backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + bind(Some(local), backlog) + + def bindAuto(backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = bind(None, backlog) + + def bind(local: Option[SocketAddress], backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(local.map(_.jSocketAddress).orNull, backlog)).refineToOrDie[IOException].unit + + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit + + def socket(implicit trace: Trace): UIO[JServerSocket] = + ZIO.succeed(channel.socket()) + + def localAddress(implicit trace: Trace): IO[IOException, SocketAddress] = + ZIO.attempt(SocketAddress.fromJava(channel.getLocalAddress())).refineToOrDie[IOException] + +} + +object ServerSocketChannel { + + def open(implicit trace: Trace): ZIO[Scope, IOException, ServerSocketChannel] = + ZIO.attempt(new ServerSocketChannel(JServerSocketChannel.open())).refineToOrDie[IOException].toNioScoped + + def fromJava(javaChannel: JServerSocketChannel): ServerSocketChannel = new ServerSocketChannel(javaChannel) +} diff --git a/nio/src/main/scala/zio/nio/channels/SelectionKey.scala b/nio/jvm/src/main/scala/zio/nio/channels/SelectionKey.scala similarity index 69% rename from nio/src/main/scala/zio/nio/channels/SelectionKey.scala rename to nio/jvm/src/main/scala/zio/nio/channels/SelectionKey.scala index 35667a12..d0850f59 100644 --- a/nio/src/main/scala/zio/nio/channels/SelectionKey.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/SelectionKey.scala @@ -1,6 +1,6 @@ package zio.nio.channels -import zio.{IO, UIO, ZIO} +import zio.{Trace, UIO, ZIO} import java.nio.{channels => jc} @@ -65,10 +65,10 @@ final class SelectionKey(private[nio] val selectionKey: jc.SelectionKey) { * case channel: ServerSocketChannel if readyOps(Operation.Accept) => * // use `channel` to accept connection * case channel: SocketChannel => - * IO.when(readyOps(Operation.Read)) { + * ZIO.when(readyOps(Operation.Read)) { * // use `channel` to read * } *> - * IO.when(readyOps(Operation.Write)) { + * ZIO.when(readyOps(Operation.Write)) { * // use `channel` to write * } * } } @@ -84,7 +84,7 @@ final class SelectionKey(private[nio] val selectionKey: jc.SelectionKey) { */ def matchChannel[R, E, A]( matcher: Set[Operation] => PartialFunction[SelectableChannel, ZIO[R, E, A]] - ): ZIO[R, E, A] = + )(implicit trace: Trace): ZIO[R, E, A] = readyOps.flatMap( matcher(_) .applyOrElse(channel, (channel: SelectableChannel) => ZIO.dieMessage(s"Unexpected channel type: $channel")) @@ -92,42 +92,46 @@ final class SelectionKey(private[nio] val selectionKey: jc.SelectionKey) { final def selector: Selector = new Selector(selectionKey.selector()) - final def isValid: UIO[Boolean] = IO.effectTotal(selectionKey.isValid) + final def isValid(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selectionKey.isValid) - final def cancel: UIO[Unit] = IO.effectTotal(selectionKey.cancel()) + final def cancel(implicit trace: Trace): UIO[Unit] = ZIO.succeed(selectionKey.cancel()) - final def interestOps: UIO[Set[Operation]] = IO.effectTotal(Operation.fromInt(selectionKey.interestOps())) + final def interestOps(implicit trace: Trace): UIO[Set[Operation]] = + ZIO.succeed(Operation.fromInt(selectionKey.interestOps())) - final def interestOps(ops: Set[Operation]): UIO[Unit] = - IO.effectTotal(selectionKey.interestOps(Operation.toInt(ops))).unit + final def interestOps(ops: Set[Operation])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(selectionKey.interestOps(Operation.toInt(ops))).unit - def interested(op: Operation): UIO[Set[Operation]] = + def interested(op: Operation)(implicit trace: Trace): UIO[Set[Operation]] = for { current <- interestOps newInterest = current + op _ <- interestOps(newInterest) } yield newInterest - def notInterested(op: Operation): UIO[Set[Operation]] = + def notInterested(op: Operation)(implicit trace: Trace): UIO[Set[Operation]] = for { current <- interestOps newInterest = current - op _ <- interestOps(newInterest) } yield newInterest - final def readyOps: UIO[Set[Operation]] = IO.effectTotal(Operation.fromInt(selectionKey.readyOps())) + final def readyOps(implicit trace: Trace): UIO[Set[Operation]] = + ZIO.succeed(Operation.fromInt(selectionKey.readyOps())) - final def isReadable: UIO[Boolean] = IO.effectTotal(selectionKey.isReadable()) + final def isReadable(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selectionKey.isReadable()) - final def isWritable: UIO[Boolean] = IO.effectTotal(selectionKey.isWritable()) + final def isWritable(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selectionKey.isWritable()) - final def isConnectable: UIO[Boolean] = IO.effectTotal(selectionKey.isConnectable()) + final def isConnectable(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selectionKey.isConnectable()) - final def isAcceptable: UIO[Boolean] = IO.effectTotal(selectionKey.isAcceptable()) + final def isAcceptable(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selectionKey.isAcceptable()) - final def attach(ob: Option[AnyRef]): UIO[Option[AnyRef]] = IO.effectTotal(Option(selectionKey.attach(ob.orNull))) + final def attach(ob: Option[AnyRef])(implicit trace: Trace): UIO[Option[AnyRef]] = + ZIO.succeed(Option(selectionKey.attach(ob.orNull))) - final def attachment: UIO[Option[AnyRef]] = IO.effectTotal(selectionKey.attachment()).map(Option(_)) + final def attachment(implicit trace: Trace): UIO[Option[AnyRef]] = + ZIO.succeed(selectionKey.attachment()).map(Option(_)) override def toString: String = selectionKey.toString diff --git a/nio/src/main/scala/zio/nio/channels/Selector.scala b/nio/jvm/src/main/scala/zio/nio/channels/Selector.scala similarity index 71% rename from nio/src/main/scala/zio/nio/channels/Selector.scala rename to nio/jvm/src/main/scala/zio/nio/channels/Selector.scala index 6d58646d..482026da 100644 --- a/nio/src/main/scala/zio/nio/channels/Selector.scala +++ b/nio/jvm/src/main/scala/zio/nio/channels/Selector.scala @@ -1,10 +1,9 @@ package zio.nio package channels -import com.github.ghik.silencer.silent -import zio.duration.Duration import zio.nio.channels.spi.SelectorProvider -import zio.{IO, Managed, UIO, ZIO} + +import zio.{Duration, IO, Scope, Trace, UIO, ZIO} import java.io.IOException import java.nio.channels.{SelectionKey => JSelectionKey, Selector => JSelector} @@ -22,14 +21,14 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { type Env = Any - val isOpen: UIO[Boolean] = IO.effectTotal(selector.isOpen) + def isOpen(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(selector.isOpen) - val provider: UIO[SelectorProvider] = - IO.effectTotal(selector.provider()).map(new SelectorProvider(_)) + def provider(implicit trace: Trace): UIO[SelectorProvider] = + ZIO.succeed(selector.provider()).map(new SelectorProvider(_)) - @silent - val keys: UIO[Set[SelectionKey]] = - IO.effectTotal(selector.keys()) + def keys(implicit trace: Trace): UIO[Set[SelectionKey]] = + ZIO + .succeed(selector.keys()) .map(_.asScala.toSet[JSelectionKey].map(new SelectionKey(_))) /** @@ -39,9 +38,9 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * an object to the key set will cause an `UnsupportedOperationException` to be thrown. The selected-key set is not * thread-safe. */ - @silent - val selectedKeys: UIO[mutable.Set[SelectionKey]] = - IO.effectTotal(selector.selectedKeys()) + def selectedKeys(implicit trace: Trace): UIO[mutable.Set[SelectionKey]] = + ZIO + .succeed(selector.selectedKeys()) .map(_.asScala.map(new SelectionKey(_))) /** @@ -50,13 +49,13 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * If the result of effect is true, the key will be removed from the selected-key set, which is usually what you want * after successfully handling a selected key. */ - def foreachSelectedKey[R, E](f: SelectionKey => ZIO[R, E, Boolean]): ZIO[R, E, Unit] = - ZIO.effectTotal(selector.selectedKeys().iterator()).flatMap { iter => + def foreachSelectedKey[R, E](f: SelectionKey => ZIO[R, E, Boolean])(implicit trace: Trace): ZIO[R, E, Unit] = + ZIO.succeed(selector.selectedKeys().iterator()).flatMap { iter => def loop: ZIO[R, E, Unit] = - ZIO.effectSuspendTotal { + ZIO.suspendSucceed { if (iter.hasNext) { val key = iter.next() - f(new SelectionKey(key)).flatMap(ZIO.when(_)(ZIO.effectTotal(iter.remove()))) *> + f(new SelectionKey(key)).flatMap(ZIO.when(_)(ZIO.succeed(iter.remove()))) *> loop } else ZIO.unit @@ -65,7 +64,8 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { loop } - def removeKey(key: SelectionKey): UIO[Unit] = IO.effectTotal(selector.selectedKeys().remove(key.selectionKey)).unit + def removeKey(key: SelectionKey)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(selector.selectedKeys().remove(key.selectionKey)).unit /** * Selects a set of keys whose corresponding channels are ready for I/O operations. This method performs a @@ -75,8 +75,8 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * @return * The number of keys, possibly zero, whose ready-operation sets were updated by the selection operation. */ - val selectNow: IO[IOException, Int] = - IO.effect(selector.selectNow()).refineToOrDie[IOException] + def selectNow(implicit trace: Trace): IO[IOException, Int] = + ZIO.attempt(selector.selectNow()).refineToOrDie[IOException] /** * Performs a blocking select operation. @@ -89,8 +89,13 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * @return * The number of keys, possibly zero, whose ready-operation sets were updated */ - def select(timeout: Duration): IO[IOException, Int] = - IO.effect(selector.select(timeout.toMillis)).refineToOrDie[IOException].fork.flatMap(_.join).onInterrupt(wakeup) + def select(timeout: Duration)(implicit trace: Trace): IO[IOException, Int] = + ZIO + .attemptBlocking(selector.select(timeout.toMillis)) + .refineToOrDie[IOException] + .fork + .flatMap(_.join) + .onInterrupt(wakeup) /** * Performs a blocking select operation. @@ -103,8 +108,8 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * @return * The number of keys, possibly zero, whose ready-operation sets were updated */ - def select: IO[IOException, Int] = - IO.effect(selector.select()).refineToOrDie[IOException].fork.flatMap(_.join).onInterrupt(wakeup) + def select(implicit trace: Trace): IO[IOException, Int] = + ZIO.attemptBlocking(selector.select()).refineToOrDie[IOException].fork.flatMap(_.join).onInterrupt(wakeup) /** * Causes the first selection operation that has not yet returned to return immediately. @@ -116,7 +121,7 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * `select(long)` methods will block as usual unless this method is invoked again in the meantime. Invoking this * method more than once between two successive selection operations has the same effect as invoking it just once. */ - def wakeup: IO[Nothing, Unit] = IO.effectTotal(selector.wakeup()).unit + def wakeup(implicit trace: Trace): IO[Nothing, Unit] = ZIO.succeed(selector.wakeup()).unit /** * Closes this selector. @@ -128,7 +133,8 @@ final class Selector(private[nio] val selector: JSelector) extends IOCloseable { * to use it, except by invoking this method or the wakeup method, will cause a `ClosedSelectorException` to be raised * as a defect. */ - def close: IO[IOException, Unit] = IO.effect(selector.close()).refineToOrDie[IOException] + def close(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(selector.close()).refineToOrDie[IOException] } @@ -137,7 +143,7 @@ object Selector { /** * Opens a selector. */ - val open: Managed[IOException, Selector] = - IO.effect(new Selector(JSelector.open())).refineToOrDie[IOException].toNioManaged + def open(implicit trace: Trace): ZIO[Scope, IOException, Selector] = + ZIO.attempt(new Selector(JSelector.open())).refineToOrDie[IOException].toNioScoped } diff --git a/nio/jvm/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala b/nio/jvm/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala new file mode 100644 index 00000000..d9237eb7 --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala @@ -0,0 +1,59 @@ +package zio.nio.channels.spi + +import zio.nio.channels._ + +import zio.{IO, Trace, ZIO} + +import java.io.IOException +import java.net.ProtocolFamily +import java.nio.channels.spi.{SelectorProvider => JSelectorProvider} +import java.nio.{channels => jc} +import zio.nio.channels.{SocketChannel, ServerSocketChannel} +import zio.nio.channels.Selector +import zio.nio.channels.DatagramChannel +import zio.nio.channels.Pipe + +final class SelectorProvider(private val selectorProvider: JSelectorProvider) { + + def openDatagramChannel(implicit trace: Trace): IO[IOException, DatagramChannel] = + ZIO.attempt(DatagramChannel.fromJava(selectorProvider.openDatagramChannel())).refineToOrDie[IOException] + + // this can throw UnsupportedOperationException - doesn't seem like a recoverable exception + def openDatagramChannel( + family: ProtocolFamily + )(implicit trace: Trace): IO[IOException, DatagramChannel] = + ZIO.attempt(DatagramChannel.fromJava(selectorProvider.openDatagramChannel(family))).refineToOrDie[IOException] + + def openPipe(implicit trace: Trace): IO[IOException, Pipe] = + ZIO.attempt(Pipe.fromJava(selectorProvider.openPipe())).refineToOrDie[IOException] + + def openSelector(implicit trace: Trace): IO[IOException, Selector] = + ZIO.attempt(new Selector(selectorProvider.openSelector())).refineToOrDie[IOException] + + def openServerSocketChannel(implicit trace: Trace): IO[IOException, ServerSocketChannel] = + ZIO.attempt(ServerSocketChannel.fromJava(selectorProvider.openServerSocketChannel())).refineToOrDie[IOException] + + def openSocketChannel(implicit trace: Trace): IO[IOException, SocketChannel] = + ZIO.attempt(new SocketChannel(selectorProvider.openSocketChannel())).refineToOrDie[IOException] + + def inheritedChannel(implicit trace: Trace): IO[IOException, Option[Channel]] = + ZIO + .attempt(Option(selectorProvider.inheritedChannel())) + .map { + _.collect { + case c: jc.SocketChannel => SocketChannel.fromJava(c) + case c: jc.ServerSocketChannel => ServerSocketChannel.fromJava(c) + case c: jc.DatagramChannel => DatagramChannel.fromJava(c) + case c: jc.FileChannel => FileChannel.fromJava(c) + } + } + .refineToOrDie[IOException] + +} + +object SelectorProvider { + + final def default(implicit trace: Trace): IO[Nothing, SelectorProvider] = + ZIO.succeed(JSelectorProvider.provider()).map(new SelectorProvider(_)) + +} diff --git a/nio/jvm/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala b/nio/jvm/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala new file mode 100644 index 00000000..e6d1ca61 --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala @@ -0,0 +1,14 @@ +package zio.nio.file + +import zio.{Trace, ZIO, Scope} +import java.io.IOException +import java.nio.{file => jf} +import zio.nio.IOCloseableManagement + +trait FileSystemPlatformSpecific { self => + + def jFileSystem: jf.FileSystem + + def newWatchService(implicit trace: Trace): ZIO[Scope, IOException, WatchService] = + ZIO.attemptBlockingIO(WatchService.fromJava(jFileSystem.newWatchService())).toNioScoped +} diff --git a/nio/jvm/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala b/nio/jvm/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala new file mode 100644 index 00000000..f28f8197 --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala @@ -0,0 +1,20 @@ +package zio.nio.file + +import zio.{Trace, ZIO} + +import Files._ +import zio.stream.ZSink +import java.io.IOException + +trait FilesPlatformSpecific { + + def deleteRecursive(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Long] = + newDirectoryStream(path).mapZIO { p => + for { + deletedInSubDirectory <- deleteRecursive(p).whenZIO(isDirectory(p)).map(_.getOrElse(0L)) + deletedFile <- deleteIfExists(p).whenZIO(isRegularFile(p)).map(_.getOrElse(false)).map(if (_) 1 else 0) + } yield deletedInSubDirectory + deletedFile + } + .run(ZSink.sum) <* delete(path) + +} diff --git a/nio/jvm/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala b/nio/jvm/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala new file mode 100644 index 00000000..eccbbf27 --- /dev/null +++ b/nio/jvm/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala @@ -0,0 +1,12 @@ +package zio.nio.file + +import zio._ + +import java.io.IOException + +trait WatchServicePlatformSpecific { + + def forDefaultFileSystem(implicit trace: Trace): ZIO[Scope, IOException, WatchService] = + FileSystem.default.newWatchService + +} diff --git a/nio/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala b/nio/jvm/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala similarity index 56% rename from nio/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala rename to nio/jvm/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala index 49514bc4..3680b3da 100644 --- a/nio/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala +++ b/nio/jvm/src/test/scala/zio/nio/channels/AsynchronousChannelGroupSpec.scala @@ -1,10 +1,9 @@ package zio.nio.channels -import zio.ZIO -import zio.duration.Duration import zio.nio.BaseSpec import zio.test.Assertion._ import zio.test._ +import zio.{Duration, Trace, ZIO} import java.nio.channels.{AsynchronousChannelGroup => JAsynchronousChannelGroup} import java.util.concurrent.{ExecutorService => JExecutorService, Executors, TimeUnit} @@ -12,76 +11,82 @@ import scala.concurrent.ExecutionContext object AsynchronousChannelGroupSpec extends BaseSpec { - override def spec: Spec[Any, TestFailure[Throwable], TestSuccess] = + override def spec: Spec[Any, Throwable] = suite("AsynchronousChannelGroupSpec")( - testM("awaitTermination") { + test("awaitTermination") { ClassFixture.providedFixture { fixture => fixture.testObj .awaitTermination(Duration.apply(1, TimeUnit.SECONDS)) .map(result => assert(result)(isFalse)) } }, - testM("failing awaitTermination") { + test("failing awaitTermination") { new AsynchronousChannelGroup(null) .awaitTermination(Duration.apply(1, TimeUnit.SECONDS)) - .run + .exit .map(result => assert(result)(dies(isSubtype[NullPointerException](anything)))) }, - testM("isShutdown") { + test("isShutdown") { ClassFixture.providedFixture { fixture => fixture.testObj.isShutdown .map(result => assert(result)(isFalse)) } }, - testM("isTerminated") { + test("isTerminated") { ClassFixture.providedFixture { fixture => fixture.testObj.isTerminated .map(result => assert(result)(isFalse)) } }, - testM("shutdown") { + test("shutdown") { ClassFixture.providedFixture { fixture => fixture.testObj.shutdown .map(_ => assertCompletes) } }, - testM("shutdownNow") { + test("shutdownNow") { ClassFixture.providedFixture { fixture => fixture.testObj.shutdownNow .map(_ => assertCompletes) } }, - testM("failing shutdownNow") { + test("failing shutdownNow") { for { - channel <- ZIO.effect(new AsynchronousChannelGroup(null)) - result <- channel.shutdownNow.run + channel <- ZIO.attempt(new AsynchronousChannelGroup(null)) + result <- channel.shutdownNow.exit } yield assert(result)(dies(anything)) }, - testM("companion object create instance using executor and initial size") { - ZIO(ExecutionContext.fromExecutorService(Executors.newCachedThreadPool())) - .bracket(executor => ZIO.effectTotal(executor.shutdown())) { executor => - AsynchronousChannelGroup(executor, 1).run.map(result => assert(result.toEither)(isRight(anything))) - } + test("companion object create instance using executor and initial size") { + ZIO.acquireReleaseWith { + ZIO.attempt(ExecutionContext.fromExecutorService(Executors.newCachedThreadPool())) + } { executor => + ZIO.succeed(executor.shutdown()) + } { executor => + AsynchronousChannelGroup(executor, 1).exit.map(result => assert(result.toEither)(isRight(anything))) + } }, - testM("failing companion object create instance using executor and initial size") { + test("failing companion object create instance using executor and initial size") { for { - result <- AsynchronousChannelGroup(null, 1).run + result <- AsynchronousChannelGroup(null, 1).exit } yield assert(result)(dies(isSubtype[NullPointerException](anything))) }, - testM("failing companion object create instance using threads no and threads factory") { + test("failing companion object create instance using threads no and threads factory") { for { - result <- AsynchronousChannelGroup(1, null).run + result <- AsynchronousChannelGroup(1, null).exit } yield assert(result)(dies(isSubtype[NullPointerException](anything))) }, - testM("companion object create instance using executor service") { - ZIO(ExecutionContext.fromExecutorService(Executors.newCachedThreadPool())) - .bracket(executor => ZIO.effectTotal(executor.shutdown())) { executor => - AsynchronousChannelGroup(executor).run.map(result => assert(result.toEither)(isRight(anything))) - } + test("companion object create instance using executor service") { + ZIO.acquireReleaseWith { + ZIO.attempt(ExecutionContext.fromExecutorService(Executors.newCachedThreadPool())) + } { executor => + ZIO.succeed(executor.shutdown()) + } { executor => + AsynchronousChannelGroup(executor).exit.map(result => assert(result.toEither)(isRight(anything))) + } }, - testM("failing companion object create instance using executor service") { + test("failing companion object create instance using executor service") { for { - result <- AsynchronousChannelGroup(null).run + result <- AsynchronousChannelGroup(null).exit } yield assert(result)(dies(isSubtype[NullPointerException](anything))) } ) @@ -112,7 +117,15 @@ object AsynchronousChannelGroupSpec extends BaseSpec { } } - def providedFixture(f: ClassFixture => ZIO[Any, Throwable, TestResult]): ZIO[Any, Throwable, TestResult] = - ZIO(ClassFixture()).bracket(fixture => ZIO.effectTotal(fixture.cleanFixture()))(fixture => f(fixture)) + def providedFixture(f: ClassFixture => ZIO[Any, Throwable, TestResult])(implicit + trace: Trace + ): ZIO[Any, Throwable, TestResult] = + ZIO.acquireReleaseWith { + ZIO.attempt(ClassFixture()) + } { fixture => + ZIO.succeed(fixture.cleanFixture()) + } { fixture => + f(fixture) + } } } diff --git a/nio/jvm/src/test/scala/zio/nio/channels/ChannelSpecJVM.scala b/nio/jvm/src/test/scala/zio/nio/channels/ChannelSpecJVM.scala new file mode 100644 index 00000000..ce00a705 --- /dev/null +++ b/nio/jvm/src/test/scala/zio/nio/channels/ChannelSpecJVM.scala @@ -0,0 +1,267 @@ +package zio.nio.channels + +import zio._ +import zio.nio.{BaseSpec, Buffer, InetSocketAddress, SocketAddress} +import zio.test.Assertion._ +import zio.test._ + +import java.io.IOException +import java.nio.channels +import java.{nio => jnio} + +object ChannelSpecJVM extends BaseSpec { + + override def spec = + suite("Channel")( + test("localAddress") { + SocketChannel.open.flatMap { con => + for { + _ <- con.bindAuto + localAddress <- con.localAddress + } yield assert(localAddress)(isSome(isSubtype[InetSocketAddress](anything))) + } + }, + suite("AsynchronousSocketChannel")( + test("read/write") { + def echoServer(started: Promise[Nothing, SocketAddress])(implicit trace: Trace): IO[Exception, Unit] = + for { + sink <- Buffer.byte(3) + _ <- ZIO.scoped { + AsynchronousServerSocketChannel.open.flatMap { server => + for { + _ <- server.bindAuto() + addr <- server.localAddress.flatMap(opt => ZIO.attempt(opt.get).orDie) + _ <- started.succeed(addr) + _ <- ZIO.scoped { + server.accept.flatMap { worker => + worker.read(sink) *> + sink.flip *> + worker.write(sink) + } + } + } yield () + } + }.fork + } yield () + + def echoClient(address: SocketAddress)(implicit trace: Trace): IO[Exception, Boolean] = + for { + src <- Buffer.byte(3) + result <- ZIO.scoped { + AsynchronousSocketChannel.open.flatMap { client => + for { + _ <- client.connect(address) + sent <- src.array + _ = sent.update(0, 1) + _ <- client.write(src) + _ <- src.flip + _ <- client.read(src) + received <- src.array + } yield sent.sameElements(received) + } + } + } yield result + + for { + serverStarted <- Promise.make[Nothing, SocketAddress] + _ <- echoServer(serverStarted) + address <- serverStarted.await + same <- echoClient(address) + } yield assert(same)(isTrue) + }, + test("read should fail when connection close") { + def server(started: Promise[Nothing, SocketAddress])(implicit + trace: Trace + ): IO[Exception, Fiber[Exception, Boolean]] = + for { + result <- ZIO.scoped { + AsynchronousServerSocketChannel.open.flatMap { server => + for { + _ <- server.bindAuto() + addr <- server.localAddress.flatMap(opt => ZIO.attempt(opt.get).orDie) + _ <- started.succeed(addr) + result <- ZIO.scoped { + server.accept + .flatMap(worker => + worker.readChunk(3) *> worker.readChunk(3) *> ZIO.succeed(false) + ) + }.catchSome { case _: java.io.EOFException => + ZIO.succeed(true) + } + } yield result + } + }.fork + } yield result + + def client(address: SocketAddress)(implicit trace: Trace): IO[Exception, Unit] = + for { + _ <- ZIO.scoped { + AsynchronousSocketChannel.open.flatMap { client => + for { + _ <- client.connect(address) + _ = client.writeChunk(Chunk.fromArray(Array[Byte](1, 1, 1))) + } yield () + } + } + } yield () + + for { + serverStarted <- Promise.make[Nothing, SocketAddress] + serverFiber <- server(serverStarted) + addr <- serverStarted.await + _ <- client(addr) + same <- serverFiber.join + } yield assert(same)(isTrue) + }, + test("close channel unbind port") { + def client(address: SocketAddress)(implicit trace: Trace): IO[Exception, Unit] = + ZIO.scoped { + AsynchronousSocketChannel.open.flatMap { + _.connect(address) + } + } + + def server( + started: Promise[Nothing, SocketAddress] + )(implicit trace: Trace): ZIO[Scope, IOException, Fiber[Exception, Unit]] = + for { + server <- AsynchronousServerSocketChannel.open + _ <- server.bindAuto() + addr <- server.localAddress.someOrElseZIO(ZIO.die(new NoSuchElementException)) + _ <- started.succeed(addr) + worker <- server.accept.unit.forkScoped + } yield worker + + for { + serverStarted1 <- Promise.make[Nothing, SocketAddress] + _ <- ZIO.scoped { + server(serverStarted1).flatMap { s1 => + serverStarted1.await.flatMap(client).zipRight(s1.join) + } + } + serverStarted2 <- Promise.make[Nothing, SocketAddress] + _ <- ZIO.scoped { + server(serverStarted2).flatMap { s2 => + serverStarted2.await.flatMap(client).zipRight(s2.join) + } + } + } yield assertCompletes + }, + test("read can be interrupted") { + live { + ZIO.scoped { + AsynchronousServerSocketChannel.open + .tap(_.bindAuto()) + .flatMap { serverChannel => + for { + serverAddress <- + serverChannel.localAddress.someOrElseZIO(ZIO.dieMessage("Local address must be bound")) + promise <- Promise.make[Nothing, Unit] + fiber <- ZIO.scoped { + AsynchronousSocketChannel.open + .tap(_.connect(serverAddress)) + .flatMap(channel => promise.succeed(()) *> channel.readChunk(1)) + }.fork + _ <- promise.await + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + } + } + }, + test("accept can be interrupted") { + live { + ZIO.scoped { + AsynchronousServerSocketChannel.open.tap(_.bindAuto()).flatMap { serverChannel => + for { + fiber <- ZIO.scoped(serverChannel.accept).fork + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + } + } + } + ), + suite("blocking operations")( + test("read can be interrupted") { + live { + for { + promise <- Promise.make[Nothing, Unit] + fiber <- ZIO.scoped { + Pipe.open + .flatMap(_.source) + .flatMapNioBlockingOps(ops => promise.succeed(()) *> ops.readChunk(1)) + + }.fork + _ <- promise.await + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + }, + test("write can be interrupted") { + val hangingOps: GatheringByteOps = new GatheringByteOps { + override protected[channels] val channel = new jnio.channels.GatheringByteChannel { + + @volatile private var _closed = false + + private def hang(): Nothing = { + while (!_closed) + Thread.sleep(10L) + throw new jnio.channels.AsynchronousCloseException() + } + + override def write(srcs: Array[jnio.ByteBuffer], offset: Int, length: Int): Long = hang() + + override def write(srcs: Array[jnio.ByteBuffer]): Long = hang() + + override def write(src: jnio.ByteBuffer): Int = hang() + + override def isOpen: Boolean = !_closed + + override def close(): Unit = _closed = true + } + } + val hangingChannel = new BlockingChannel { + override type BlockingOps = GatheringByteOps + + override def flatMapBlocking[R, E >: IOException, A]( + f: GatheringByteOps => ZIO[R, E, A] + )(implicit trace: Trace): ZIO[R, E, A] = nioBlocking(f(hangingOps)) + + override protected val channel: channels.Channel = hangingOps.channel + } + + live { + for { + promise <- Promise.make[Nothing, Unit] + fiber <- + hangingChannel + .flatMapBlocking(ops => promise.succeed(()) *> ops.writeChunk(Chunk.single(42.toByte))) + .fork + _ <- promise.await + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + }, + test("accept can be interrupted") { + live { + ZIO.scoped { + ServerSocketChannel.open.tap(_.bindAuto()).flatMap { serverChannel => + for { + promise <- Promise.make[Nothing, Unit] + fiber <- serverChannel.flatMapBlocking(ops => promise.succeed(()) *> ZIO.scoped(ops.accept)).fork + _ <- promise.await + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + } + } + } + ) + ) +} diff --git a/nio/jvm/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala b/nio/jvm/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala new file mode 100644 index 00000000..afb54e33 --- /dev/null +++ b/nio/jvm/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala @@ -0,0 +1,93 @@ +package zio.nio.channels + +import zio._ +import zio.nio._ +import zio.test.Assertion._ +import zio.test._ + +import java.io.IOException + +object DatagramChannelSpec extends BaseSpec { + + override def spec: Spec[TestEnvironment with Scope, Any] = + suite("DatagramChannelSpec")( + test("read/write") { + def echoServer(started: Promise[Nothing, SocketAddress])(implicit trace: Trace): UIO[Unit] = + for { + sink <- Buffer.byte(3) + _ <- ZIO.scoped { + DatagramChannel.open.flatMapNioBlocking { (server, ops) => + for { + _ <- server.bindAuto + addr <- server.localAddress.someOrElseZIO(ZIO.dieMessage("Must have local address")) + _ <- started.succeed(addr) + addr <- ops.receive(sink) + _ <- sink.flip + _ <- ops.send(sink, addr) + } yield () + } + }.fork + } yield () + + def echoClient(address: SocketAddress)(implicit trace: Trace): IO[IOException, Boolean] = + for { + src <- Buffer.byte(3) + result <- ZIO.scoped { + DatagramChannel.open.flatMapNioBlockingOps { client => + for { + _ <- client.connect(address) + sent <- src.array + _ = sent.update(0, 1) + _ <- client.send(src, address) + _ <- src.flip + _ <- client.read(src) + received <- src.array + } yield sent.sameElements(received) + } + } + } yield result + + for { + serverStarted <- Promise.make[Nothing, SocketAddress] + _ <- echoServer(serverStarted) + addr <- serverStarted.await + same <- echoClient(addr) + } yield assert(same)(isTrue) + }, + test("close channel unbind port") { + def client(address: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.scoped { + DatagramChannel.open.flatMapNioBlockingOps(_.connect(address).unit) + } + + def server( + address: Option[SocketAddress], + started: Promise[Nothing, SocketAddress] + )(implicit trace: Trace): UIO[Fiber[IOException, Unit]] = + for { + worker <- ZIO.scoped { + DatagramChannel.open.flatMapNioBlocking { (server, _) => + for { + _ <- server.bind(address) + addr <- server.localAddress.someOrElseZIO(ZIO.dieMessage("Local address must be bound")) + _ <- started.succeed(addr) + } yield () + } + }.fork + } yield worker + + for { + serverStarted <- Promise.make[Nothing, SocketAddress] + s1 <- server(None, serverStarted) + addr <- serverStarted.await + _ <- client(addr) + _ <- s1.join + serverStarted2 <- Promise.make[Nothing, SocketAddress] + s2 <- server(Some(addr), serverStarted2) + _ <- serverStarted2.await + _ <- client(addr) + _ <- s2.join + } yield assertCompletes + } + ) +} diff --git a/nio/src/test/scala/zio/nio/channels/FileChannelSpec.scala b/nio/jvm/src/test/scala/zio/nio/channels/FileChannelSpec.scala similarity index 61% rename from nio/src/test/scala/zio/nio/channels/FileChannelSpec.scala rename to nio/jvm/src/test/scala/zio/nio/channels/FileChannelSpec.scala index 3d9f1d94..e2718a94 100644 --- a/nio/src/test/scala/zio/nio/channels/FileChannelSpec.scala +++ b/nio/jvm/src/test/scala/zio/nio/channels/FileChannelSpec.scala @@ -1,16 +1,12 @@ package zio.nio.channels -import zio.blocking.Blocking -import zio.clock.Clock import zio.nio.charset.Charset import zio.nio.file.{Files, Path} import zio.nio.{BaseSpec, Buffer} -import zio.random.Random -import zio.stream.Stream +import zio.stream.ZStream import zio.test.Assertion._ import zio.test._ -import zio.test.environment.{Live, TestClock, TestConsole, TestRandom, TestSystem} -import zio.{Chunk, Has, URIO, ZIO, blocking} +import zio.{Chunk, Trace, UIO, ZIO} import java.io.EOFException import java.nio.file.StandardOpenOption @@ -18,26 +14,23 @@ import scala.io.Source object FileChannelSpec extends BaseSpec { - private val readFile = Path("nio/src/test/resources/async_file_read_test.txt") + private val readFile = Path("nio/shared/src/test/resources/async_file_read_test.txt") private val readFileContents = "Hello World" - def loadViaSource(path: Path): URIO[Blocking, List[String]] = - ZIO - .effect(Source.fromFile(path.toFile)) - .bracket(s => ZIO.effectTotal(s.close()))(s => blocking.effectBlocking(s.getLines().toList)) - .orDie + def loadViaSource(path: Path)(implicit trace: Trace): UIO[List[String]] = + ZIO.acquireReleaseWith { + ZIO.succeed(Source.fromFile(path.toFile)) + } { source => + ZIO.succeed(source.close()) + } { source => + ZIO.succeedBlocking(source.getLines().toList) + } - override def spec: Spec[Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = + override def spec = suite("FileChannelSpec")( - testM("asynchronous file buffer read") { - AsynchronousFileChannel.open(readFile, StandardOpenOption.READ).use { channel => + test("asynchronous file buffer read") { + AsynchronousFileChannel.open(readFile, StandardOpenOption.READ).flatMap { channel => for { buffer <- Buffer.byte(16) _ <- channel.read(buffer, 0) @@ -47,23 +40,23 @@ object FileChannelSpec extends BaseSpec { } yield assert(text)(equalTo(readFileContents)) } }, - testM("asynchronous file chunk read") { - AsynchronousFileChannel.open(readFile, StandardOpenOption.READ).use { channel => + test("asynchronous file chunk read") { + AsynchronousFileChannel.open(readFile, StandardOpenOption.READ).flatMap { channel => for { bytes <- channel.readChunk(500, 0L) chars <- Charset.Standard.utf8.decodeChunk(bytes) } yield assert(chars.mkString)(equalTo(readFileContents)) } }, - testM("asynchronous file write") { - val path = Path("nio/src/test/resources/async_file_write_test.txt") + test("asynchronous file write") { + val path = Path("nio/shared/src/test/resources/async_file_write_test.txt") AsynchronousFileChannel .open( path, StandardOpenOption.CREATE, StandardOpenOption.WRITE ) - .use { channel => + .flatMap { channel => for { buffer <- Buffer.byte(Chunk.fromArray("Hello World".getBytes)) _ <- channel.write(buffer, 0) @@ -73,11 +66,11 @@ object FileChannelSpec extends BaseSpec { } }, - testM("memory mapped buffer") { + test("memory mapped buffer") { for { result <- FileChannel .open(readFile, StandardOpenOption.READ) - .useNioBlockingOps { ops => + .flatMapNioBlockingOps { ops => for { buffer <- ops.map(FileChannel.MapMode.READ_ONLY, 0L, 6L) bytes <- buffer.getChunk() @@ -86,10 +79,10 @@ object FileChannelSpec extends BaseSpec { } } yield result }, - testM("end of stream") { + test("end of stream") { FileChannel .open(readFile, StandardOpenOption.READ) - .useNioBlocking { (channel, ops) => + .flatMapNioBlocking { (channel, ops) => for { size <- channel.size _ <- ops.readChunk(size.toInt) @@ -99,23 +92,23 @@ object FileChannelSpec extends BaseSpec { .flip .map(assert(_)(isSubtype[EOFException](anything))) }, - testM("stream reading") { + test("stream reading") { FileChannel .open(readFile, StandardOpenOption.READ) - .useNioBlockingOps { - _.stream().transduce(Charset.Standard.utf8.newDecoder.transducer()).runCollect.map(_.mkString) + .flatMapNioBlockingOps { + _.stream().via(Charset.Standard.utf8.newDecoder.transducer()).runCollect.map(_.mkString) } .map(assert(_)(equalTo(readFileContents))) }, - testM("sink writing") { + test("sink writing") { val testData = """Yet such is oft the course of deeds that move the wheels of the world: | small hands do them because they must, while the eyes of the great are elsewhere.""".stripMargin - val stream = Stream.fromIterable(testData).transduce(Charset.Standard.utf8.newEncoder.transducer()) - val file = Path("nio/src/test/resources/sink_write_test.txt") + val stream = ZStream.fromIterable(testData).via(Charset.Standard.utf8.newEncoder.transducer()) + val file = Path("nio/shared/src/test/resources/sink_write_test.txt") FileChannel .open(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW) - .useNioBlockingOps(channel => stream.run(channel.sink())) + .flatMapNioBlockingOps(channel => stream.run(channel.sink())) .zipRight(loadViaSource(file).ensuring(Files.delete(file).orDie)) .map(lines => assert(lines.mkString("\n"))(equalTo(testData))) } diff --git a/nio/jvm/src/test/scala/zio/nio/channels/SelectorSpec.scala b/nio/jvm/src/test/scala/zio/nio/channels/SelectorSpec.scala new file mode 100644 index 00000000..7dab8c94 --- /dev/null +++ b/nio/jvm/src/test/scala/zio/nio/channels/SelectorSpec.scala @@ -0,0 +1,126 @@ +package zio.nio.channels + +import zio._ +import zio.nio.channels.SelectionKey.Operation +import zio.nio.{BaseSpec, Buffer, ByteBuffer, SocketAddress} +import zio.test.Assertion._ +import zio.test._ + +import java.io.IOException +import java.nio.channels.CancelledKeyException + +object SelectorSpec extends BaseSpec { + + override def spec: Spec[TestEnvironment with Scope, Any] = + suite("SelectorSpec")( + test("read/write") { + for { + started <- Promise.make[Nothing, SocketAddress] + serverFiber <- ZIO.scoped(server(started)).fork + addr <- started.await + clientFiber <- client(addr).fork + _ <- serverFiber.join + message <- clientFiber.join + } yield assert(message)(equalTo("Hello world")) + }, + test("select is interruptible") { + live { + ZIO.scoped { + Selector.open.flatMap { selector => + for { + fiber <- selector.select.fork + _ <- ZIO.sleep(500.milliseconds) + exit <- fiber.interrupt + } yield assert(exit)(isInterrupted) + } + } + } + } + ) + + def byteArrayToString(array: Array[Byte]): String = array.takeWhile(_ != 10).map(_.toChar).mkString.trim + + def safeStatusCheck(statusCheck: IO[CancelledKeyException, Boolean])(implicit + trace: Trace + ): IO[Nothing, Boolean] = + statusCheck.fold(_ => false, identity) + + def server( + started: Promise[Nothing, SocketAddress] + )(implicit trace: Trace): ZIO[Scope, Exception, Unit] = { + def serverLoop( + scope: Scope, + selector: Selector, + buffer: ByteBuffer + )(implicit trace: Trace): ZIO[Any, Exception, Unit] = + for { + _ <- selector.select + _ <- selector.foreachSelectedKey { key => + key.matchChannel { readyOps => + { + case channel: ServerSocketChannel if readyOps(Operation.Accept) => + for { + scopeResult <- scope.extend(channel.flatMapNonBlocking(_.accept)) + maybeClient = scopeResult + _ <- ZIO.whenCase(maybeClient) { case Some(client) => + client.configureBlocking(false) *> client.register(selector, Set(Operation.Read)) + } + } yield () + case channel: SocketChannel if readyOps(Operation.Read) => + channel.flatMapNonBlocking { client => + for { + _ <- client.read(buffer) + _ <- buffer.flip + _ <- client.write(buffer) + _ <- buffer.clear + _ <- channel.close + } yield () + } + } + } + .as(true) + } + _ <- selector.selectedKeys.filterOrDieMessage(_.isEmpty)("Selected key set should be empty") + } yield () + + for { + scope <- ZIO.scope + selector <- Selector.open + channel <- ServerSocketChannel.open + _ <- for { + _ <- channel.bindAuto() + _ <- channel.configureBlocking(false) + _ <- channel.register(selector, Set(Operation.Accept)) + buffer <- Buffer.byte(256) + addr <- channel.localAddress + _ <- started.succeed(addr) + + /* + * we need to run the server loop twice: + * 1. to accept the client request + * 2. to read from the client channel + */ + _ <- serverLoop(scope, selector, buffer).repeat(Schedule.once) + } yield () + } yield () + } + + def client(address: SocketAddress)(implicit trace: Trace): IO[IOException, String] = { + val bytes = Chunk.fromArray("Hello world".getBytes) + for { + buffer <- Buffer.byte(bytes) + text <- ZIO.scoped { + SocketChannel.open(address).flatMapNioBlockingOps { client => + for { + _ <- client.write(buffer) + _ <- buffer.clear + _ <- client.read(buffer) + array <- buffer.array + text = byteArrayToString(array) + _ <- buffer.clear + } yield text + } + } + } yield text + } +} diff --git a/nio/jvm/src/test/scala/zio/nio/file/WathServiceSpec.scala b/nio/jvm/src/test/scala/zio/nio/file/WathServiceSpec.scala new file mode 100644 index 00000000..b92b8d22 --- /dev/null +++ b/nio/jvm/src/test/scala/zio/nio/file/WathServiceSpec.scala @@ -0,0 +1,24 @@ +package zio.nio.file + +import zio.Scope +import zio.nio.BaseSpec +import zio.test.Assertion._ +import zio.test._ + +import java.io.IOException +import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE + +object WathServiceSpec extends BaseSpec { + + override def spec: Spec[Scope, IOException] = + suite("WatchServiceSpec")( + test("Watch Service register")( + FileSystem.default.newWatchService.flatMap { watchService => + for { + watchKey <- Path("nio/shared/src/test/resources").register(watchService, ENTRY_CREATE) + watchable = watchKey.watchable + } yield assert(watchable)(equalTo(Path("nio/shared/src/test/resources"))) + } + ) + ) +} diff --git a/nio/native/src/main/scala-2/zio/nio/channels/package.scala b/nio/native/src/main/scala-2/zio/nio/channels/package.scala new file mode 100644 index 00000000..3559d97f --- /dev/null +++ b/nio/native/src/main/scala-2/zio/nio/channels/package.scala @@ -0,0 +1,24 @@ +package zio.nio + +import zio.{Trace, ZIO} + +import java.io.IOException + +package object channels { + + implicit final class BlockingNioOps[-R, +C <: BlockingChannel]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + + def flatMapNioBlocking[R1, E >: IOException, A]( + f: (C, C#BlockingOps) => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1 with Any, E, A] = + underlying.flatMap(c => c.flatMapBlocking(f(c, _))) + + def flatMapNioBlockingOps[R1, E >: IOException, A]( + f: C#BlockingOps => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1 with Any, E, A] = flatMapNioBlocking((_, ops) => f(ops)) + + } + +} diff --git a/nio/native/src/main/scala-3/zio/nio/channels/package.scala b/nio/native/src/main/scala-3/zio/nio/channels/package.scala new file mode 100644 index 00000000..b544434c --- /dev/null +++ b/nio/native/src/main/scala-3/zio/nio/channels/package.scala @@ -0,0 +1,25 @@ +package zio.nio + + +import zio.{ZIO, Trace} + +import java.io.IOException + +package object channels { + + implicit final class BlockingNioOps[-R, BO, C <: BlockingChannel { type BlockingOps <: BO }]( + private val underlying: ZIO[R, IOException, C] + ) extends AnyVal { + type F1[R, E, A] = (C, BO) => ZIO[R, E, A] + + def flatMapNioBlocking[R1, E >: IOException, A]( + f: F1[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1, E, A] = underlying.flatMap(c => c.flatMapBlocking(f(c, _))) + + def flatMapNioBlockingOps[R1, E >: IOException, A]( + f: BO => ZIO[R1, E, A] + )(implicit trace: Trace): ZIO[R with R1, E, A] = flatMapNioBlocking((_, ops) => f(ops)) + + } + +} diff --git a/nio/native/src/main/scala/zio/nio/ZStreamHelper.scala b/nio/native/src/main/scala/zio/nio/ZStreamHelper.scala new file mode 100644 index 00000000..84e683f5 --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/ZStreamHelper.scala @@ -0,0 +1,47 @@ +package zio.nio + +import zio.stream.ZStream +import zio.{Scope, ZIO, Trace} + +/** + * A mutable buffer of shorts. + */ +object ZStreamHelper { + + /** + * Creates a stream from a Java stream + */ + final def fromJavaStream[A](stream: => java.util.stream.Stream[A])(implicit + trace: Trace + ): ZStream[Any, Throwable, A] = + fromJavaStream(stream, ZStream.DefaultChunkSize) + + /** + * Creates a stream from a Java stream + */ + final def fromJavaStream[A]( + stream: => java.util.stream.Stream[A], + chunkSize: Int + )(implicit trace: Trace): ZStream[Any, Throwable, A] = + ZStream.fromJavaIteratorScoped( + ZIO.acquireRelease(ZIO.attempt(stream))(stream => ZIO.succeed(stream.close())).map(_.iterator()), + chunkSize + ) + + /** + * Creates a stream from a scoped Java stream + */ + final def fromJavaStreamScoped[R, A](stream: => ZIO[Scope with R, Throwable, java.util.stream.Stream[A]])(implicit + trace: Trace + ): ZStream[R, Throwable, A] = + fromJavaStreamScoped[R, A](stream, ZStream.DefaultChunkSize) + + /** + * Creates a stream from a scoped Java stream + */ + final def fromJavaStreamScoped[R, A]( + stream: => ZIO[Scope with R, Throwable, java.util.stream.Stream[A]], + chunkSize: Int + )(implicit trace: Trace): ZStream[R, Throwable, A] = + ZStream.scoped[R](stream).flatMap(fromJavaStream(_, chunkSize)) +} diff --git a/nio/native/src/main/scala/zio/nio/channels/AsynchronousChannel.scala b/nio/native/src/main/scala/zio/nio/channels/AsynchronousChannel.scala new file mode 100644 index 00000000..b3ed1b58 --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/channels/AsynchronousChannel.scala @@ -0,0 +1,130 @@ +package zio.nio +package channels +import zio._ + +import java.io.IOException +import java.net.SocketOption +import java.nio.channels.{ + AsynchronousByteChannel => JAsynchronousByteChannel, + AsynchronousServerSocketChannel => JAsynchronousServerSocketChannel, + AsynchronousSocketChannel => JAsynchronousSocketChannel +} + +/** + * A byte channel that reads and writes asynchronously. + * + * The read and write operations will never block the calling thread. + */ +abstract class AsynchronousByteChannel private[channels] (protected val channel: JAsynchronousByteChannel) + extends Channel {} + +object AsynchronousByteChannel {} + +final class AsynchronousServerSocketChannel(protected val channel: JAsynchronousServerSocketChannel) extends Channel { + + def bindTo(local: SocketAddress, backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + bind(Some(local), backlog) + + def bindAuto(backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = bind(None, backlog) + + /** + * Binds the channel's socket to a local address and configures the socket to listen for connections, up to backlog + * pending connection. + */ + def bind(address: Option[SocketAddress], backlog: Int = 0)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(address.map(_.jSocketAddress).orNull, backlog)).refineToOrDie[IOException].unit + + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit + + /** + * The `SocketAddress` that the socket is bound to, or the `SocketAddress` representing the loopback address if denied + * by the security manager, or `Maybe.empty` if the channel's socket is not bound. + */ + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getLocalAddress).map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] + +} + +object AsynchronousServerSocketChannel { + + def open(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousServerSocketChannel] = + ZIO + .attempt(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open())) + .refineToOrDie[IOException] + .toNioScoped + + def open( + channelGroup: AsynchronousChannelGroup + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousServerSocketChannel] = + ZIO + .attempt(new AsynchronousServerSocketChannel(JAsynchronousServerSocketChannel.open(channelGroup.channelGroup))) + .refineToOrDie[IOException] + .toNioScoped + + def fromJava(channel: JAsynchronousServerSocketChannel): AsynchronousServerSocketChannel = + new AsynchronousServerSocketChannel(channel) + +} + +final class AsynchronousSocketChannel(override protected val channel: JAsynchronousSocketChannel) + extends AsynchronousByteChannel(channel) { + + def bindTo(address: SocketAddress)(implicit trace: Trace): IO[IOException, Unit] = bind(Some(address)) + + def bindAuto(implicit trace: Trace): IO[IOException, Unit] = bind(None) + + def bind(address: Option[SocketAddress])(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.bind(address.map(_.jSocketAddress).orNull)).refineToOrDie[IOException].unit + + def setOption[T](name: SocketOption[T], value: T)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.setOption(name, value)).refineToOrDie[IOException].unit + + def shutdownInput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownInput()).refineToOrDie[IOException].unit + + def shutdownOutput(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.shutdownOutput()).refineToOrDie[IOException].unit + + def remoteAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getRemoteAddress) + .map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] + + def localAddress(implicit trace: Trace): IO[IOException, Option[SocketAddress]] = + ZIO + .attempt( + Option(channel.getLocalAddress) + .map(SocketAddress.fromJava) + ) + .refineToOrDie[IOException] + +} + +object AsynchronousSocketChannel { + + def open(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousSocketChannel] = + ZIO + .attempt(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open())) + .refineToOrDie[IOException] + .toNioScoped + + def open( + channelGroup: AsynchronousChannelGroup + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousSocketChannel] = + ZIO + .attempt(new AsynchronousSocketChannel(JAsynchronousSocketChannel.open(channelGroup.channelGroup))) + .refineToOrDie[IOException] + .toNioScoped + + def fromJava(asyncSocketChannel: JAsynchronousSocketChannel): AsynchronousSocketChannel = + new AsynchronousSocketChannel(asyncSocketChannel) + +} diff --git a/nio/native/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala b/nio/native/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala new file mode 100644 index 00000000..c0cca695 --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/channels/AsynchronousFileChannel.scala @@ -0,0 +1,60 @@ +package zio.nio +package channels +import zio._ +import zio.nio.file.Path + +import java.io.IOException +import java.nio.channels.{AsynchronousFileChannel => JAsynchronousFileChannel} +import java.nio.file.OpenOption +import java.nio.file.attribute.FileAttribute +import scala.concurrent.ExecutionContextExecutorService +import scala.jdk.CollectionConverters._ + +final class AsynchronousFileChannel(protected val channel: JAsynchronousFileChannel) extends Channel { + + def force(metaData: Boolean)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.force(metaData)).refineToOrDie[IOException] + + def size(implicit trace: Trace): IO[IOException, Long] = ZIO.attempt(channel.size()).refineToOrDie[IOException] + + def truncate(size: Long)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.truncate(size)).refineToOrDie[IOException].unit + + def tryLock( + position: Long = 0L, + size: Long = Long.MaxValue, + shared: Boolean = false + )(implicit trace: Trace): IO[IOException, FileLock] = + ZIO.attempt(new FileLock(channel.tryLock(position, size, shared))).refineToOrDie[IOException] + +} + +object AsynchronousFileChannel { + + def open(file: Path, options: OpenOption*)(implicit + trace: Trace + ): ZIO[Scope, IOException, AsynchronousFileChannel] = + ZIO + .attempt(new AsynchronousFileChannel(JAsynchronousFileChannel.open(file.javaPath, options: _*))) + .refineToOrDie[IOException] + .toNioScoped + + def open( + file: Path, + options: Set[OpenOption], + executor: Option[ExecutionContextExecutorService], + attrs: Set[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Scope, IOException, AsynchronousFileChannel] = + ZIO + .attempt( + new AsynchronousFileChannel( + JAsynchronousFileChannel.open(file.javaPath, options.asJava, executor.orNull, attrs.toSeq: _*) + ) + ) + .refineToOrDie[IOException] + .toNioScoped + + def fromJava(javaAsynchronousFileChannel: JAsynchronousFileChannel): AsynchronousFileChannel = + new AsynchronousFileChannel(javaAsynchronousFileChannel) + +} diff --git a/nio/native/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala b/nio/native/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala new file mode 100644 index 00000000..802ea143 --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/file/FileSystemPlatformSpecific.scala @@ -0,0 +1,7 @@ +package zio.nio.file + +import java.nio.{file => jf} + +trait FileSystemPlatformSpecific { self => + def jFileSystem: jf.FileSystem +} diff --git a/nio/native/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala b/nio/native/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala new file mode 100644 index 00000000..19f11011 --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/file/FilesPlatformSpecific.scala @@ -0,0 +1,29 @@ +package zio.nio.file + +import java.nio.file.{Files => JFiles, SimpleFileVisitor, Path => JPath} +import java.io.IOException +import zio.{Trace, ZIO} +import java.nio.file.FileVisitResult +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.FileVisitor + +trait FilesPlatformSpecific { + + private val visitator: FileVisitor[JPath] = new SimpleFileVisitor[JPath]() { + + override def visitFile(file: JPath, attrs: BasicFileAttributes) = { + JFiles.delete(file) + FileVisitResult.CONTINUE + } + + override def postVisitDirectory(dir: JPath, exc: IOException) = { + JFiles.delete(dir) + FileVisitResult.CONTINUE + } + + } + + def deleteRecursive(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Long] = + ZIO.attemptBlockingIO(JFiles.walkFileTree(path.javaPath, visitator)) *> ZIO.succeed(0L) + +} diff --git a/nio/native/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala b/nio/native/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala new file mode 100644 index 00000000..6f7c466f --- /dev/null +++ b/nio/native/src/main/scala/zio/nio/file/WatchServicePlatformSpecific.scala @@ -0,0 +1,3 @@ +package zio.nio.file + +trait WatchServicePlatformSpecific diff --git a/nio/src/main/scala/zio/nio/Buffer.scala b/nio/shared/src/main/scala/zio/nio/Buffer.scala similarity index 77% rename from nio/src/main/scala/zio/nio/Buffer.scala rename to nio/shared/src/main/scala/zio/nio/Buffer.scala index 1602e5f9..a5ccbffb 100644 --- a/nio/src/main/scala/zio/nio/Buffer.scala +++ b/nio/shared/src/main/scala/zio/nio/Buffer.scala @@ -1,6 +1,6 @@ package zio.nio -import zio.{Chunk, UIO, ZIO} +import zio.{Chunk, Trace, UIO, ZIO} import java.nio.{ Buffer => JBuffer, @@ -13,7 +13,6 @@ import java.nio.{ LongBuffer => JLongBuffer, ShortBuffer => JShortBuffer } -import scala.reflect.ClassTag /** * Mutable buffer of value elements. @@ -53,7 +52,7 @@ import scala.reflect.ClassTag * View buffers are constructed via the various `asXXX` methods on the [[zio.nio.ByteBuffer]] class, for example: * * {{{ - * val ints: UIO[IntBuffer] = bytes.asIntBuffer + * val ints(implicit trace: Trace): UIO[IntBuffer] = bytes.asIntBuffer * }}} * * Changes to made via view buffers are reflected in the original `ByteBuffer` and vice-versa. @@ -83,7 +82,7 @@ import scala.reflect.ClassTag * }}} */ @specialized // See if Specialized will work on return values, e.g. `get` -abstract class Buffer[A: ClassTag] private[nio] () { +abstract class Buffer[A] private[nio] () { protected[nio] val buffer: JBuffer @@ -97,12 +96,12 @@ abstract class Buffer[A: ClassTag] private[nio] () { * * Also the byte order used any view buffers created from this buffer. */ - def order: UIO[ByteOrder] + def order(implicit trace: Trace): UIO[ByteOrder] /** * Returns this buffer's position. */ - final def position: UIO[Int] = UIO.effectTotal(buffer.position) + final def position(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.position) /** * Sets this buffer's position. @@ -112,7 +111,8 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @param newPosition * Must be >= 0 and <= the current limit. */ - final def position(newPosition: Int): UIO[Unit] = UIO.effectTotal(buffer.position(newPosition)).unit + final def position(newPosition: Int)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.position(newPosition)).unit /** * Moves this buffer's position forward or backwards by a delta. @@ -122,7 +122,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @return * The new position. */ - final def movePosition(delta: Int): UIO[Int] = + final def movePosition(delta: Int)(implicit trace: Trace): UIO[Int] = for { pos <- position newPos = pos + delta @@ -132,7 +132,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { /** * Returns this buffer's limit. */ - final def limit: UIO[Int] = UIO.effectTotal(buffer.limit) + final def limit(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.limit) /** * Sets this buffer's limit. @@ -142,7 +142,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @param newLimit * Must be >= 0 and <= this buffer's capacity. */ - final def limit(newLimit: Int): UIO[Unit] = UIO.effectTotal(buffer.limit(newLimit)).unit + final def limit(newLimit: Int)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.limit(newLimit)).unit /** * Moves this buffer's limit forward or backwards by a delta. @@ -152,7 +152,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @return * The new limit. */ - final def moveLimit(delta: Int): UIO[Int] = + final def moveLimit(delta: Int)(implicit trace: Trace): UIO[Int] = for { pos <- limit newPos = pos + delta @@ -162,24 +162,24 @@ abstract class Buffer[A: ClassTag] private[nio] () { /** * Returns the number of elements between this buffer's position and its limit. */ - final def remaining: UIO[Int] = UIO.effectTotal(buffer.remaining) + final def remaining(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.remaining) /** * Indicates whether there are any elements between this buffer's position and its limit. */ - final def hasRemaining: UIO[Boolean] = UIO.effectTotal(buffer.hasRemaining) + final def hasRemaining(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(buffer.hasRemaining) /** * Sets this buffer's mark to the current position. */ - final def mark: UIO[Unit] = UIO.effectTotal(buffer.mark()).unit + final def mark(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.mark()).unit /** * Resets the position to the previously set mark. A mark ''must'' be set before calling this. * * Dies with `InvalidMarkException` if a mark has not previously been set. */ - final def reset: UIO[Unit] = UIO.effectTotal(buffer.reset()).unit + final def reset(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.reset()).unit /** * Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded. No @@ -189,7 +189,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * If the buffer's current values have not been completely processed, then the `compact` method may be more * appropriate. */ - final def clear: UIO[Unit] = UIO.effectTotal(buffer.clear()).unit + final def clear(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.clear()).unit /** * Flips this buffer. The limit is set to the current position and then the position is set to zero. If the mark is @@ -199,13 +199,13 @@ abstract class Buffer[A: ClassTag] private[nio] () { * This method is often used in conjunction with the `compact` method when transferring data from one place to * another. */ - final def flip: UIO[Unit] = UIO.effectTotal(buffer.flip()).unit + final def flip(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.flip()).unit /** * Rewinds this buffer. The position is set to zero and the mark is discarded. Invoke this method before a sequence of * channel-write or get operations, assuming that the limit has already been set appropriately. */ - final def rewind: UIO[Unit] = UIO.effectTotal(buffer.rewind()).unit + final def rewind(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.rewind()).unit /** * Indicates if this buffer is read-only. @@ -236,7 +236,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * The new buffer's position will be zero, its capacity and its limit will be the number of bytes remaining in this * buffer. */ - def slice: UIO[Buffer[A]] + def slice(implicit trace: Trace): UIO[Buffer[A]] /** * Compacts this buffer (optional operation). The bytes between the buffer's current position and its limit, if any, @@ -252,7 +252,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * * Dies with `ReadOnlyBufferException` if this buffer is read-only. */ - def compact: UIO[Unit] + def compact(implicit trace: Trace): UIO[Unit] /** * Creates a new buffer that shares this buffer's content. The content of the new buffer will be that of this buffer. @@ -261,7 +261,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * * The new buffer's capacity, limit, position, and mark values will be identical to those of this buffer. */ - def duplicate: UIO[Buffer[A]] + def duplicate(implicit trace: Trace): UIO[Buffer[A]] /** * Perform effects using this buffer's underlying array directly. Because only some buffers are backed by arrays, two @@ -280,31 +280,31 @@ abstract class Buffer[A: ClassTag] private[nio] () { final def withArray[R, E, B]( noArray: ZIO[R, E, B], hasArray: (Array[A], Int) => ZIO[R, E, B] - ): ZIO[R, E, B] = + )(implicit trace: Trace): ZIO[R, E, B] = if (buffer.hasArray) for { a <- array - offset <- UIO.effectTotal(buffer.arrayOffset()) + offset <- ZIO.succeed(buffer.arrayOffset()) result <- hasArray(a, offset) } yield result else noArray - protected[nio] def array: UIO[Array[A]] + protected[nio] def array(implicit trace: Trace): UIO[Array[A]] /** * Relative get of a single element. Reads the element at the position and increments the position. * * Dies with `BufferUnderflowException` If there are no elements remaining. */ - def get: UIO[A] + def get(implicit trace: Trace): UIO[A] /** * Absolute get of a single element. Reads the element at the given index. The position does not change. * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit. */ - def get(i: Int): UIO[A] + def get(i: Int)(implicit trace: Trace): UIO[A] /** * Relative get of multiple elements. @@ -315,7 +315,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @param maxLength * Defaults to `Int.MaxValue`, meaning all remaining elements will be read. */ - def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[A]] + def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[A]] /** * Relative put of a single element. Writes the element at the position and increments the position. @@ -323,7 +323,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * Dies with `BufferOverflowException` if there are no elements remaining. Dies with `ReadOnlyBufferException` if this * is a read-only buffer. */ - def put(element: A): UIO[Unit] + def put(element: A)(implicit trace: Trace): UIO[Unit] /** * Absolute put of a single element. Writes the element at the specified index. The position does not change. @@ -331,14 +331,14 @@ abstract class Buffer[A: ClassTag] private[nio] () { * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - def put(index: Int, element: A): UIO[Unit] + def put(index: Int, element: A)(implicit trace: Trace): UIO[Unit] /** * Tries to put an entire chunk in this buffer, possibly overflowing. * * `putChunk` is a safe public variant of this that won't overflow. */ - protected def putChunkAll(chunk: Chunk[A]): UIO[Unit] + protected def putChunkAll(chunk: Chunk[A])(implicit trace: Trace): UIO[Unit] /** * Relative put of multiple elements. Writes as many elements as can fit in remaining buffer space, returning any @@ -347,7 +347,7 @@ abstract class Buffer[A: ClassTag] private[nio] () { * @return * The remaining elements that could not fit in this buffer, if any. */ - final def putChunk(chunk: Chunk[A]): UIO[Chunk[A]] = + final def putChunk(chunk: Chunk[A])(implicit trace: Trace): UIO[Chunk[A]] = for { r <- remaining (putChunk, remainderChunk) = chunk.splitAt(r) @@ -357,12 +357,18 @@ abstract class Buffer[A: ClassTag] private[nio] () { /** * Creates a read-only view of this buffer. */ - def asReadOnlyBuffer: UIO[Buffer[A]] + def asReadOnlyBuffer(implicit trace: Trace): UIO[Buffer[A]] } object Buffer { + // Transforms NegativeArraySizeException to IllegalArgumentException due to scala-native specifications. + private def transformError[A](effect: UIO[A]): UIO[A] = + effect.catchSomeDefect { case _: NegativeArraySizeException => + ZIO.succeed(throw new IllegalArgumentException()) + } + /** * Allocates a byte buffer backed by a new array. * @@ -373,7 +379,8 @@ object Buffer { * @param capacity * The number of bytes to allocate. */ - def byte(capacity: Int): UIO[ByteBuffer] = UIO.effectTotal(byteFromJava(JByteBuffer.allocate(capacity))) + def byte(capacity: Int)(implicit trace: Trace): UIO[ByteBuffer] = + Buffer transformError ZIO.succeed(byteFromJava(JByteBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -384,7 +391,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def byte(chunk: Chunk[Byte]): UIO[ByteBuffer] = UIO.effectTotal(byteFromJava(JByteBuffer.wrap(chunk.toArray))) + def byte(chunk: Chunk[Byte])(implicit trace: Trace): UIO[ByteBuffer] = + Buffer transformError ZIO.succeed(byteFromJava(JByteBuffer.wrap(chunk.toArray))) /** * Allocates a direct byte buffer. @@ -396,7 +404,8 @@ object Buffer { * @param capacity * The number of bytes to allocate. */ - def byteDirect(capacity: Int): UIO[ByteBuffer] = UIO.effectTotal(byteFromJava(JByteBuffer.allocateDirect(capacity))) + def byteDirect(capacity: Int)(implicit trace: Trace): UIO[ByteBuffer] = + Buffer transformError ZIO.succeed(byteFromJava(JByteBuffer.allocateDirect(capacity))) /** * Wraps an existing Java `ByteBuffer`. @@ -415,7 +424,8 @@ object Buffer { * @param capacity * The number of characters to allocate. */ - def char(capacity: Int): UIO[CharBuffer] = UIO.effectTotal(charFromJava(JCharBuffer.allocate(capacity))) + def char(capacity: Int)(implicit trace: Trace): UIO[CharBuffer] = + Buffer transformError ZIO.succeed(charFromJava(JCharBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -426,7 +436,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def char(chunk: Chunk[Char]): UIO[CharBuffer] = UIO.effectTotal(charFromJava(JCharBuffer.wrap(chunk.toArray))) + def char(chunk: Chunk[Char])(implicit trace: Trace): UIO[CharBuffer] = + Buffer transformError ZIO.succeed(charFromJava(JCharBuffer.wrap(chunk.toArray))) /** * Creates a read-only character buffer wrapping a character sequence. @@ -446,7 +457,8 @@ object Buffer { charSequence: CharSequence, start: Int, end: Int - ): UIO[CharBuffer] = UIO.effectTotal(charFromJava(JCharBuffer.wrap(charSequence, start, end))) + )(implicit trace: Trace): UIO[CharBuffer] = + Buffer transformError ZIO.succeed(charFromJava(JCharBuffer.wrap(charSequence, start, end))) /** * Creates a read-only character buffer wrapping a character sequence. @@ -456,8 +468,8 @@ object Buffer { * @param charSequence * The characters to wrap. */ - def char(charSequence: CharSequence): UIO[CharBuffer] = - UIO.effectTotal(new CharBuffer(JCharBuffer.wrap(charSequence))) + def char(charSequence: CharSequence)(implicit trace: Trace): UIO[CharBuffer] = + Buffer transformError ZIO.succeed(new CharBuffer(JCharBuffer.wrap(charSequence))) /** * Wraps an existing Java `CharBuffer`. @@ -476,7 +488,8 @@ object Buffer { * @param capacity * The number of floats to allocate. */ - def float(capacity: Int): UIO[FloatBuffer] = UIO.effectTotal(floatFromJava(JFloatBuffer.allocate(capacity))) + def float(capacity: Int)(implicit trace: Trace): UIO[FloatBuffer] = + Buffer transformError ZIO.succeed(floatFromJava(JFloatBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -487,7 +500,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def float(chunk: Chunk[Float]): UIO[FloatBuffer] = UIO.effectTotal(floatFromJava(JFloatBuffer.wrap(chunk.toArray))) + def float(chunk: Chunk[Float])(implicit trace: Trace): UIO[FloatBuffer] = + Buffer transformError ZIO.succeed(floatFromJava(JFloatBuffer.wrap(chunk.toArray))) /** * Wraps an existing Java `FloatBuffer`. @@ -506,7 +520,8 @@ object Buffer { * @param capacity * The number of doubles to allocate. */ - def double(capacity: Int): UIO[DoubleBuffer] = UIO.effectTotal(doubleFromJava(JDoubleBuffer.allocate(capacity))) + def double(capacity: Int)(implicit trace: Trace): UIO[DoubleBuffer] = + Buffer transformError ZIO.succeed(doubleFromJava(JDoubleBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -517,8 +532,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def double(chunk: Chunk[Double]): UIO[DoubleBuffer] = - UIO.effectTotal(doubleFromJava(JDoubleBuffer.wrap(chunk.toArray))) + def double(chunk: Chunk[Double])(implicit trace: Trace): UIO[DoubleBuffer] = + Buffer transformError ZIO.succeed(doubleFromJava(JDoubleBuffer.wrap(chunk.toArray))) /** * Wraps an existing Java `DoubleBuffer`. @@ -537,7 +552,8 @@ object Buffer { * @param capacity * The number of ints to allocate. */ - def int(capacity: Int): UIO[IntBuffer] = UIO.effectTotal(intFromJava(JIntBuffer.allocate(capacity))) + def int(capacity: Int)(implicit trace: Trace): UIO[IntBuffer] = + Buffer transformError ZIO.succeed(intFromJava(JIntBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -548,7 +564,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def int(chunk: Chunk[Int]): UIO[IntBuffer] = UIO.effectTotal(intFromJava(JIntBuffer.wrap(chunk.toArray))) + def int(chunk: Chunk[Int])(implicit trace: Trace): UIO[IntBuffer] = + Buffer transformError ZIO.succeed(intFromJava(JIntBuffer.wrap(chunk.toArray))) /** * Wraps an existing Java `IntBuffer`. @@ -567,7 +584,8 @@ object Buffer { * @param capacity * The number of longs to allocate. */ - def long(capacity: Int): UIO[LongBuffer] = UIO.effectTotal(longFromJava(JLongBuffer.allocate(capacity))) + def long(capacity: Int)(implicit trace: Trace): UIO[LongBuffer] = + Buffer transformError ZIO.succeed(longFromJava(JLongBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -578,7 +596,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def long(chunk: Chunk[Long]): UIO[LongBuffer] = UIO.effectTotal(longFromJava(JLongBuffer.wrap(chunk.toArray))) + def long(chunk: Chunk[Long])(implicit trace: Trace): UIO[LongBuffer] = + Buffer transformError ZIO.succeed(longFromJava(JLongBuffer.wrap(chunk.toArray))) /** * Wraps an existing Java `LongBuffer`. @@ -597,7 +616,8 @@ object Buffer { * @param capacity * The number of shorts to allocate. */ - def short(capacity: Int): UIO[ShortBuffer] = UIO.effectTotal(shortFromJava(JShortBuffer.allocate(capacity))) + def short(capacity: Int)(implicit trace: Trace): UIO[ShortBuffer] = + Buffer transformError ZIO.succeed(shortFromJava(JShortBuffer.allocate(capacity))) /** * Creates a new array-backed buffer containing data copied from a chunk. @@ -608,7 +628,8 @@ object Buffer { * @param chunk * The data to copy into the new buffer. */ - def short(chunk: Chunk[Short]): UIO[ShortBuffer] = UIO.effectTotal(shortFromJava(JShortBuffer.wrap(chunk.toArray))) + def short(chunk: Chunk[Short])(implicit trace: Trace): UIO[ShortBuffer] = + Buffer transformError ZIO.succeed(shortFromJava(JShortBuffer.wrap(chunk.toArray))) /** * Wraps an existing Java `ShortBuffer`. diff --git a/nio/src/main/scala/zio/nio/ByteBuffer.scala b/nio/shared/src/main/scala/zio/nio/ByteBuffer.scala similarity index 60% rename from nio/src/main/scala/zio/nio/ByteBuffer.scala rename to nio/shared/src/main/scala/zio/nio/ByteBuffer.scala index 755a3f8c..1b9ab8cf 100644 --- a/nio/src/main/scala/zio/nio/ByteBuffer.scala +++ b/nio/shared/src/main/scala/zio/nio/ByteBuffer.scala @@ -1,6 +1,6 @@ package zio.nio -import zio.{Chunk, UIO, ZIO} +import zio.{Chunk, Trace, UIO, ZIO} import java.nio.{ByteBuffer => JByteBuffer, ByteOrder} @@ -9,20 +9,21 @@ import java.nio.{ByteBuffer => JByteBuffer, ByteOrder} */ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends Buffer[Byte] { - final override protected[nio] def array: UIO[Array[Byte]] = UIO.effectTotal(buffer.array()) + final override protected[nio] def array(implicit trace: Trace): UIO[Array[Byte]] = ZIO.succeed(buffer.array()) - final def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order()) + final def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order()) /** * Changes the byte order used by this buffer for multi-byte reads and view buffers. */ - def order(o: ByteOrder): UIO[Unit] = UIO.effectTotal(buffer.order(o)).unit + def order(o: ByteOrder)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.order(o)).unit - final override def slice: UIO[ByteBuffer] = UIO.effectTotal(new ByteBuffer(buffer.slice())) + final override def slice(implicit trace: Trace): UIO[ByteBuffer] = ZIO.succeed(new ByteBuffer(buffer.slice())) - final override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit + final override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit - final override def duplicate: UIO[ByteBuffer] = UIO.effectTotal(new ByteBuffer(buffer.duplicate())) + final override def duplicate(implicit trace: Trace): UIO[ByteBuffer] = + ZIO.succeed(new ByteBuffer(buffer.duplicate())) /** * Provides the underlying Java byte buffer for use in an effect. @@ -32,11 +33,11 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * @return * The effect value constructed by `f` using the underlying buffer. */ - def withJavaBuffer[R, E, A](f: JByteBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) + def withJavaBuffer[R, E, A](f: JByteBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) - final override def get: UIO[Byte] = UIO.effectTotal(buffer.get()) + final override def get(implicit trace: Trace): UIO[Byte] = ZIO.succeed(buffer.get()) - final override def get(i: Int): UIO[Byte] = UIO.effectTotal(buffer.get(i)) + final override def get(i: Int)(implicit trace: Trace): UIO[Byte] = ZIO.succeed(buffer.get(i)) /** * Reads bytes from the current position and returns them in a `Chunk`. @@ -44,38 +45,48 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * @param maxLength * Defaults to `Int.MaxValue`, meaning all remaining elements will be read. */ - final override def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[Byte]] = - UIO.effectTotal { + final override def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[Byte]] = + ZIO.succeed { val array = Array.ofDim[Byte](math.min(maxLength, buffer.remaining())) buffer.get(array) Chunk.fromArray(array) } - final override def put(element: Byte): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit + final override def put(element: Byte)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(element)).unit - final override def put(index: Int, element: Byte): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit + final override def put(index: Int, element: Byte)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit - def putByteBuffer(source: ByteBuffer): UIO[Unit] = UIO.effectTotal(buffer.put(source.buffer)).unit + def putByteBuffer(source: ByteBuffer)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(source.buffer)).unit - final override protected def putChunkAll(chunk: Chunk[Byte]): UIO[Unit] = - UIO.effectTotal { + final override protected def putChunkAll(chunk: Chunk[Byte])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { val array = chunk.toArray buffer.put(array) }.unit - final override def asReadOnlyBuffer: UIO[ByteBuffer] = UIO.effectTotal(new ByteBuffer(buffer.asReadOnlyBuffer())) + final override def asReadOnlyBuffer(implicit trace: Trace): UIO[ByteBuffer] = + ZIO.succeed(new ByteBuffer(buffer.asReadOnlyBuffer())) - final def asCharBuffer: UIO[CharBuffer] = UIO.effectTotal(new CharBuffer(buffer.asCharBuffer())) + final def asCharBuffer(implicit trace: Trace): UIO[CharBuffer] = + ZIO.succeed(new CharBuffer(buffer.asCharBuffer())) - final def asDoubleBuffer: UIO[DoubleBuffer] = UIO.effectTotal(new DoubleBuffer(buffer.asDoubleBuffer())) + final def asDoubleBuffer(implicit trace: Trace): UIO[DoubleBuffer] = + ZIO.succeed(new DoubleBuffer(buffer.asDoubleBuffer())) - final def asFloatBuffer: UIO[FloatBuffer] = UIO.effectTotal(new FloatBuffer(buffer.asFloatBuffer())) + final def asFloatBuffer(implicit trace: Trace): UIO[FloatBuffer] = + ZIO.succeed(new FloatBuffer(buffer.asFloatBuffer())) - final def asIntBuffer: UIO[IntBuffer] = UIO.effectTotal(new IntBuffer(buffer.asIntBuffer())) + final def asIntBuffer(implicit trace: Trace): UIO[IntBuffer] = + ZIO.succeed(new IntBuffer(buffer.asIntBuffer())) - final def asLongBuffer: UIO[LongBuffer] = UIO.effectTotal(new LongBuffer(buffer.asLongBuffer())) + final def asLongBuffer(implicit trace: Trace): UIO[LongBuffer] = + ZIO.succeed(new LongBuffer(buffer.asLongBuffer())) - final def asShortBuffer: UIO[ShortBuffer] = UIO.effectTotal(new ShortBuffer(buffer.asShortBuffer())) + final def asShortBuffer(implicit trace: Trace): UIO[ShortBuffer] = + ZIO.succeed(new ShortBuffer(buffer.asShortBuffer())) /** * Relative put of a single character. Writes the character at the position using the current byte order and @@ -84,7 +95,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 2 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putChar(value: Char): UIO[Unit] = UIO.effectTotal(buffer.putChar(value)).unit + final def putChar(value: Char)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.putChar(value)).unit /** * Absolute put of a single character. Writes the character at the specified index using the current byte order. The @@ -93,7 +104,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-1. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putChar(index: Int, value: Char): UIO[Unit] = UIO.effectTotal(buffer.putChar(index, value)).unit + final def putChar(index: Int, value: Char)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putChar(index, value)).unit /** * Relative put of a single double. Writes the double at the position using the current byte order and increments the @@ -102,7 +114,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 8 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putDouble(value: Double): UIO[Unit] = UIO.effectTotal(buffer.putDouble(value)).unit + final def putDouble(value: Double)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putDouble(value)).unit /** * Absolute put of a single double. Writes the double at the specified index using the current byte order. The @@ -111,7 +124,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-7. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putDouble(index: Int, value: Double): UIO[Unit] = UIO.effectTotal(buffer.putDouble(index, value)).unit + final def putDouble(index: Int, value: Double)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putDouble(index, value)).unit /** * Relative put of a single float. Writes the float at the position using the current byte order and increments the @@ -120,7 +134,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 4 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putFloat(value: Float): UIO[Unit] = UIO.effectTotal(buffer.putFloat(value)).unit + final def putFloat(value: Float)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.putFloat(value)).unit /** * Absolute put of a single float. Writes the float at the specified index using the current byte order. The position @@ -129,7 +143,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-3. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putFloat(index: Int, value: Float): UIO[Unit] = UIO.effectTotal(buffer.putFloat(index, value)).unit + final def putFloat(index: Int, value: Float)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putFloat(index, value)).unit /** * Relative put of a single int. Writes the int at the position using the current byte order and increments the @@ -138,7 +153,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 4 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putInt(value: Int): UIO[Unit] = UIO.effectTotal(buffer.putInt(value)).unit + final def putInt(value: Int)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.putInt(value)).unit /** * Absolute put of a single int. Writes the int at the specified index using the current byte order. The position does @@ -147,7 +162,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-3. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putInt(index: Int, value: Int): UIO[Unit] = UIO.effectTotal(buffer.putInt(index, value)).unit + final def putInt(index: Int, value: Int)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putInt(index, value)).unit /** * Relative put of a single long. Writes the long at the position using the current byte order and increments the @@ -156,7 +172,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 8 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putLong(value: Long): UIO[Unit] = UIO.effectTotal(buffer.putLong(value)).unit + final def putLong(value: Long)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.putLong(value)).unit /** * Absolute put of a single long. Writes the long at the specified index using the current byte order. The position @@ -165,7 +181,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-7. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putLong(index: Int, value: Long): UIO[Unit] = UIO.effectTotal(buffer.putLong(index, value)).unit + final def putLong(index: Int, value: Long)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putLong(index, value)).unit /** * Relative put of a single short. Writes the short at the position using the current byte order and increments the @@ -174,7 +191,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `BufferOverflowException` if there are fewer than 2 bytes remaining. Dies with `ReadOnlyBufferException` * if this is a read-only buffer. */ - final def putShort(value: Short): UIO[Unit] = UIO.effectTotal(buffer.putShort(value)).unit + final def putShort(value: Short)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.putShort(value)).unit /** * Absolute put of a single short. Writes the short at the specified index using the current byte order. The position @@ -183,7 +200,8 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-1. Dies with * `ReadOnlyBufferException` if this is a read-only buffer. */ - final def putShort(index: Int, value: Short): UIO[Unit] = UIO.effectTotal(buffer.putShort(index, value)).unit + final def putShort(index: Int, value: Short)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.putShort(index, value)).unit /** * Relative get of a single character. Reads the character at the position using the current byte order and increments @@ -191,7 +209,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 2 bytes remaining. */ - final def getChar: UIO[Char] = UIO.effectTotal(buffer.getChar()) + final def getChar(implicit trace: Trace): UIO[Char] = ZIO.succeed(buffer.getChar()) /** * Absolute get of a single character. Reads the character at the given index using the current byte order. The @@ -199,7 +217,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-1. */ - final def getChar(index: Int): UIO[Char] = UIO.effectTotal(buffer.getChar(index)) + final def getChar(index: Int)(implicit trace: Trace): UIO[Char] = ZIO.succeed(buffer.getChar(index)) /** * Relative get of a single double. Reads the double at the position using the current byte order and increments the @@ -207,7 +225,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 8 bytes remaining. */ - final def getDouble: UIO[Double] = UIO.effectTotal(buffer.getDouble()) + final def getDouble(implicit trace: Trace): UIO[Double] = ZIO.succeed(buffer.getDouble()) /** * Absolute get of a single double. Reads the double at the given index using the current byte order. The position @@ -215,7 +233,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-7. */ - final def getDouble(index: Int): UIO[Double] = UIO.effectTotal(buffer.getDouble(index)) + final def getDouble(index: Int)(implicit trace: Trace): UIO[Double] = ZIO.succeed(buffer.getDouble(index)) /** * Relative get of a single float. Reads the float at the position using the current byte order and increments the @@ -223,7 +241,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 4 bytes remaining. */ - final def getFloat: UIO[Float] = UIO.effectTotal(buffer.getFloat()) + final def getFloat(implicit trace: Trace): UIO[Float] = ZIO.succeed(buffer.getFloat()) /** * Absolute get of a single float. Reads the float at the given index using the current byte order. The position does @@ -231,7 +249,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-3. */ - final def getFloat(index: Int): UIO[Float] = UIO.effectTotal(buffer.getFloat(index)) + final def getFloat(index: Int)(implicit trace: Trace): UIO[Float] = ZIO.succeed(buffer.getFloat(index)) /** * Relative get of a single int. Reads the int at the position using the current byte order and increments the @@ -239,7 +257,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 4 bytes remaining. */ - final def getInt: UIO[Int] = UIO.effectTotal(buffer.getInt()) + final def getInt(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.getInt()) /** * Absolute get of a single int. Reads the int at the given index using the current byte order. The position does not @@ -247,7 +265,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-3. */ - final def getInt(index: Int): UIO[Int] = UIO.effectTotal(buffer.getInt(index)) + final def getInt(index: Int)(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.getInt(index)) /** * Relative get of a single long. Reads the long at the position using the current byte order and increments the @@ -255,7 +273,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 8 bytes remaining. */ - final def getLong: UIO[Long] = UIO.effectTotal(buffer.getLong()) + final def getLong(implicit trace: Trace): UIO[Long] = ZIO.succeed(buffer.getLong()) /** * Absolute get of a single long. Reads the long at the given index using the current byte order. The position does @@ -263,7 +281,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-7. */ - final def getLong(index: Int): UIO[Long] = UIO.effectTotal(buffer.getLong(index)) + final def getLong(index: Int)(implicit trace: Trace): UIO[Long] = ZIO.succeed(buffer.getLong(index)) /** * Relative get of a single short. Reads the short at the position using the current byte order and increments the @@ -271,7 +289,7 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `BufferUnderflowException` If there are fewer than 2 bytes remaining. */ - final def getShort: UIO[Short] = UIO.effectTotal(buffer.getShort()) + final def getShort(implicit trace: Trace): UIO[Short] = ZIO.succeed(buffer.getShort()) /** * Absolute get of a single short. Reads the short at the given index using the current byte order. The position does @@ -279,6 +297,6 @@ class ByteBuffer protected[nio] (protected[nio] val buffer: JByteBuffer) extends * * Dies with `IndexOutOfBoundsException` if the index is negative or not smaller than the limit-1. */ - final def getShort(index: Int): UIO[Short] = UIO.effectTotal(buffer.getShort(index)) + final def getShort(index: Int)(implicit trace: Trace): UIO[Short] = ZIO.succeed(buffer.getShort(index)) } diff --git a/nio/shared/src/main/scala/zio/nio/CharBuffer.scala b/nio/shared/src/main/scala/zio/nio/CharBuffer.scala new file mode 100644 index 00000000..8f79468b --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/CharBuffer.scala @@ -0,0 +1,60 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, CharBuffer => JCharBuffer} + +/** + * A mutable buffer of characters. + */ +final class CharBuffer(protected[nio] val buffer: JCharBuffer) extends Buffer[Char] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Char]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order()) + + override def slice(implicit trace: Trace): UIO[CharBuffer] = ZIO.succeed(new CharBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[CharBuffer] = + ZIO.succeed(new CharBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java character buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java character buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JCharBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Char] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Char] = ZIO.succeed(buffer.get(i)) + + override def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[Char]] = + ZIO.succeed { + val array = Array.ofDim[Char](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + def getString(implicit trace: Trace): UIO[String] = ZIO.succeed(buffer.toString()) + + override def put(element: Char)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Char)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Char])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[CharBuffer] = + ZIO.succeed(new CharBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/shared/src/main/scala/zio/nio/DoubleBuffer.scala b/nio/shared/src/main/scala/zio/nio/DoubleBuffer.scala new file mode 100644 index 00000000..2384ab8e --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/DoubleBuffer.scala @@ -0,0 +1,60 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, DoubleBuffer => JDoubleBuffer} + +/** + * A mutable buffer of doubles. + */ +final class DoubleBuffer(protected[nio] val buffer: JDoubleBuffer) extends Buffer[Double] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Double]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order) + + override def slice(implicit trace: Trace): UIO[DoubleBuffer] = ZIO.succeed(new DoubleBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[DoubleBuffer] = + ZIO.succeed(new DoubleBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java double buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java double buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JDoubleBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Double] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Double] = ZIO.succeed(buffer.get(i)) + + override def getChunk( + maxLength: Int = Int.MaxValue + )(implicit trace: Trace): UIO[Chunk[Double]] = + ZIO.succeed { + val array = Array.ofDim[Double](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + override def put(element: Double)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Double)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Double])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[DoubleBuffer] = + ZIO.succeed(new DoubleBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/shared/src/main/scala/zio/nio/FloatBuffer.scala b/nio/shared/src/main/scala/zio/nio/FloatBuffer.scala new file mode 100644 index 00000000..a3e62292 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/FloatBuffer.scala @@ -0,0 +1,58 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, FloatBuffer => JFloatBuffer} + +/** + * A mutable buffer of floats. + */ +final class FloatBuffer(protected[nio] val buffer: JFloatBuffer) extends Buffer[Float] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Float]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order) + + override def slice(implicit trace: Trace): UIO[FloatBuffer] = ZIO.succeed(new FloatBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[FloatBuffer] = + ZIO.succeed(new FloatBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java float buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java float buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JFloatBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Float] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Float] = ZIO.succeed(buffer.get(i)) + + override def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[Float]] = + ZIO.succeed { + val array = Array.ofDim[Float](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + override def put(element: Float)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Float)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Float])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[FloatBuffer] = + ZIO.succeed(new FloatBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/src/main/scala/zio/nio/IOCloseable.scala b/nio/shared/src/main/scala/zio/nio/IOCloseable.scala similarity index 68% rename from nio/src/main/scala/zio/nio/IOCloseable.scala rename to nio/shared/src/main/scala/zio/nio/IOCloseable.scala index cfb34d0c..e00480a9 100644 --- a/nio/src/main/scala/zio/nio/IOCloseable.scala +++ b/nio/shared/src/main/scala/zio/nio/IOCloseable.scala @@ -1,6 +1,6 @@ package zio.nio -import zio.IO +import zio.{IO, Trace} import java.io.IOException @@ -12,6 +12,6 @@ trait IOCloseable { /** * Closes this resource. */ - def close: IO[IOException, Unit] + def close(implicit trace: Trace): IO[IOException, Unit] } diff --git a/nio/src/main/scala/zio/nio/InetAddress.scala b/nio/shared/src/main/scala/zio/nio/InetAddress.scala similarity index 59% rename from nio/src/main/scala/zio/nio/InetAddress.scala rename to nio/shared/src/main/scala/zio/nio/InetAddress.scala index 2d4e499c..bba2f954 100644 --- a/nio/src/main/scala/zio/nio/InetAddress.scala +++ b/nio/shared/src/main/scala/zio/nio/InetAddress.scala @@ -1,6 +1,6 @@ package zio.nio -import zio.{Chunk, IO} +import zio.{Chunk, IO, Trace, ZIO} import java.io.IOException import java.net.{InetAddress => JInetAddress, UnknownHostException} @@ -31,15 +31,16 @@ final class InetAddress private[nio] (private[nio] val jInetAddress: JInetAddres def isMCOrgLocal: Boolean = jInetAddress.isMCOrgLocal - def isReachable(timeOut: Int): IO[IOException, Boolean] = - IO.effect(jInetAddress.isReachable(timeOut)).refineToOrDie[IOException] + def isReachable(timeOut: Int)(implicit trace: Trace): IO[IOException, Boolean] = + ZIO.attempt(jInetAddress.isReachable(timeOut)).refineToOrDie[IOException] def isReachable( networkInterface: NetworkInterface, ttl: Integer, timeout: Integer - ): IO[IOException, Boolean] = - IO.effect(jInetAddress.isReachable(networkInterface.jNetworkInterface, ttl, timeout)) + )(implicit trace: Trace): IO[IOException, Boolean] = + ZIO + .attempt(jInetAddress.isReachable(networkInterface.jNetworkInterface, ttl, timeout)) .refineToOrDie[IOException] def hostName: String = jInetAddress.getHostName @@ -62,23 +63,29 @@ final class InetAddress private[nio] (private[nio] val jInetAddress: JInetAddres object InetAddress { - def byAddress(bytes: Chunk[Byte]): IO[UnknownHostException, InetAddress] = - IO.effect(new InetAddress(JInetAddress.getByAddress(bytes.toArray))) + def byAddress(bytes: Chunk[Byte])(implicit trace: Trace): IO[UnknownHostException, InetAddress] = + ZIO + .attempt(new InetAddress(JInetAddress.getByAddress(bytes.toArray))) .refineToOrDie[UnknownHostException] - def byAddress(hostname: String, bytes: Chunk[Byte]): IO[UnknownHostException, InetAddress] = - IO.effect(new InetAddress(JInetAddress.getByAddress(hostname, bytes.toArray))) + def byAddress(hostname: String, bytes: Chunk[Byte])(implicit + trace: Trace + ): IO[UnknownHostException, InetAddress] = + ZIO + .attempt(new InetAddress(JInetAddress.getByAddress(hostname, bytes.toArray))) .refineToOrDie[UnknownHostException] - def byAllName(hostName: String): IO[UnknownHostException, List[InetAddress]] = - IO.effect(JInetAddress.getAllByName(hostName).toList.map(new InetAddress(_))) + def byAllName(hostName: String)(implicit trace: Trace): IO[UnknownHostException, List[InetAddress]] = + ZIO + .attempt(JInetAddress.getAllByName(hostName).toList.map(new InetAddress(_))) .refineToOrDie[UnknownHostException] - def byName(hostName: String): IO[UnknownHostException, InetAddress] = - IO.effect(new InetAddress(JInetAddress.getByName(hostName))) + def byName(hostName: String)(implicit trace: Trace): IO[UnknownHostException, InetAddress] = + ZIO + .attempt(new InetAddress(JInetAddress.getByName(hostName))) .refineToOrDie[UnknownHostException] - def localHost: IO[UnknownHostException, InetAddress] = - IO.effect(new InetAddress(JInetAddress.getLocalHost)).refineToOrDie[UnknownHostException] + def localHost(implicit trace: Trace): IO[UnknownHostException, InetAddress] = + ZIO.attempt(new InetAddress(JInetAddress.getLocalHost)).refineToOrDie[UnknownHostException] } diff --git a/nio/src/main/scala/zio/nio/InetSocketAddress.scala b/nio/shared/src/main/scala/zio/nio/InetSocketAddress.scala similarity index 73% rename from nio/src/main/scala/zio/nio/InetSocketAddress.scala rename to nio/shared/src/main/scala/zio/nio/InetSocketAddress.scala index 70c8b99d..5f2c06a7 100644 --- a/nio/src/main/scala/zio/nio/InetSocketAddress.scala +++ b/nio/shared/src/main/scala/zio/nio/InetSocketAddress.scala @@ -1,6 +1,6 @@ package zio.nio -import zio.{IO, UIO} +import zio.{IO, Trace, UIO, ZIO} import java.net.{InetSocketAddress => JInetSocketAddress, SocketAddress => JSocketAddress, UnknownHostException} @@ -67,7 +67,7 @@ final class InetSocketAddress private[nio] (private val jInetSocketAddress: JIne * * Note: This method may trigger a name service reverse lookup if the address was created with a literal IP address. */ - def hostName: UIO[String] = UIO.effectTotal(jInetSocketAddress.getHostName) + def hostName(implicit trace: Trace): UIO[String] = ZIO.succeed(jInetSocketAddress.getHostName) /** * Returns the hostname, or the String form of the address if it doesn't have a hostname (it was created using a @@ -76,7 +76,7 @@ final class InetSocketAddress private[nio] (private val jInetSocketAddress: JIne * This has the benefit of not attempting a reverse lookup. This is an effect because the result could change if a * reverse lookup is performed, for example by calling `hostName`. */ - def hostString: UIO[String] = UIO.effectTotal(jInetSocketAddress.getHostString) + def hostString(implicit trace: Trace): UIO[String] = ZIO.succeed(jInetSocketAddress.getHostString) /** * Checks whether the address has been resolved or not. @@ -92,14 +92,15 @@ object InetSocketAddress { * * The socket address will be ''resolved''. */ - def wildCard(port: Int): UIO[InetSocketAddress] = UIO.effectTotal(new InetSocketAddress(new JInetSocketAddress(port))) + def wildCard(port: Int)(implicit trace: Trace): UIO[InetSocketAddress] = + ZIO.succeed(new InetSocketAddress(new JInetSocketAddress(port))) /** * Creates a socket address where the IP address is the wildcard address and the port is ephemeral. * * The socket address will be ''resolved''. */ - def wildCardEphemeral: UIO[InetSocketAddress] = wildCard(0) + def wildCardEphemeral(implicit trace: Trace): UIO[InetSocketAddress] = wildCard(0) /** * Creates a socket address from a hostname and a port number. @@ -107,15 +108,17 @@ object InetSocketAddress { * This method will attempt to resolve the hostname; if this fails, the returned socket address will be * ''unresolved''. */ - def hostName(hostName: String, port: Int): UIO[InetSocketAddress] = - UIO.effectTotal(new InetSocketAddress(new JInetSocketAddress(hostName, port))) + def hostName(hostName: String, port: Int)(implicit trace: Trace): UIO[InetSocketAddress] = + ZIO.succeed(new InetSocketAddress(new JInetSocketAddress(hostName, port))) /** * Creates a resolved socket address from a hostname and port number. * * If the hostname cannot be resolved, fails with `UnknownHostException`. */ - def hostNameResolved(hostName: String, port: Int): IO[UnknownHostException, InetSocketAddress] = + def hostNameResolved(hostName: String, port: Int)(implicit + trace: Trace + ): IO[UnknownHostException, InetSocketAddress] = InetAddress.byName(hostName).flatMap(inetAddress(_, port)) /** @@ -124,31 +127,35 @@ object InetSocketAddress { * This method will attempt to resolve the hostname; if this fails, the returned socket address will be * ''unresolved''. */ - def hostNameEphemeral(hostName: String): UIO[InetSocketAddress] = this.hostName(hostName, 0) + def hostNameEphemeral(hostName: String)(implicit trace: Trace): UIO[InetSocketAddress] = + this.hostName(hostName, 0) /** * Creates a resolved socket address from a hostname, with an ephemeral port. * * If the hostname cannot be resolved, fails with `UnknownHostException`. */ - def hostNameEphemeralResolved(hostName: String): IO[UnknownHostException, InetSocketAddress] = + def hostNameEphemeralResolved(hostName: String)(implicit + trace: Trace + ): IO[UnknownHostException, InetSocketAddress] = InetAddress.byName(hostName).flatMap(inetAddressEphemeral) /** * Creates a socket address from an IP address and a port number. */ - def inetAddress(address: InetAddress, port: Int): UIO[InetSocketAddress] = - UIO.effectTotal(new InetSocketAddress(new JInetSocketAddress(address.jInetAddress, port))) + def inetAddress(address: InetAddress, port: Int)(implicit trace: Trace): UIO[InetSocketAddress] = + ZIO.succeed(new InetSocketAddress(new JInetSocketAddress(address.jInetAddress, port))) /** * Creates a socket address from an IP address, with an ephemeral port. */ - def inetAddressEphemeral(address: InetAddress): UIO[InetSocketAddress] = inetAddress(address, 0) + def inetAddressEphemeral(address: InetAddress)(implicit trace: Trace): UIO[InetSocketAddress] = + inetAddress(address, 0) /** * Creates a socket address for localhost using the specified port. */ - def localHost(port: Int): IO[UnknownHostException, InetSocketAddress] = + def localHost(port: Int)(implicit trace: Trace): IO[UnknownHostException, InetSocketAddress] = InetAddress.localHost.flatMap(inetAddress(_, port)) /** @@ -157,8 +164,8 @@ object InetSocketAddress { * No attempt will be made to resolve the hostname into an `InetAddress`. The socket address will be flagged as * ''unresolved''. */ - def unresolvedHostName(hostName: String, port: Int): UIO[InetSocketAddress] = - UIO.effectTotal(new InetSocketAddress(JInetSocketAddress.createUnresolved(hostName, port))) + def unresolvedHostName(hostName: String, port: Int)(implicit trace: Trace): UIO[InetSocketAddress] = + ZIO.succeed(new InetSocketAddress(JInetSocketAddress.createUnresolved(hostName, port))) /** * Creates an unresolved socket address from a hostname using an ephemeral port. @@ -166,6 +173,7 @@ object InetSocketAddress { * No attempt will be made to resolve the hostname into an `InetAddress`. The socket address will be flagged as * ''unresolved''. */ - def unresolvedHostNameEphemeral(hostName: String): UIO[InetSocketAddress] = unresolvedHostName(hostName, 0) + def unresolvedHostNameEphemeral(hostName: String)(implicit trace: Trace): UIO[InetSocketAddress] = + unresolvedHostName(hostName, 0) } diff --git a/nio/shared/src/main/scala/zio/nio/IntBuffer.scala b/nio/shared/src/main/scala/zio/nio/IntBuffer.scala new file mode 100644 index 00000000..f001e676 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/IntBuffer.scala @@ -0,0 +1,57 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, IntBuffer => JIntBuffer} + +/** + * A mutable buffer of ints. + */ +final class IntBuffer(protected[nio] val buffer: JIntBuffer) extends Buffer[Int] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Int]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order) + + override def slice(implicit trace: Trace): UIO[IntBuffer] = ZIO.succeed(new IntBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[IntBuffer] = ZIO.succeed(new IntBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java int buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java int buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JIntBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Int] = ZIO.succeed(buffer.get(i)) + + override def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[Int]] = + ZIO.succeed { + val array = Array.ofDim[Int](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + override def put(element: Int)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Int)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Int])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[IntBuffer] = + ZIO.succeed(new IntBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/src/main/scala/zio/nio/InterfaceAddress.scala b/nio/shared/src/main/scala/zio/nio/InterfaceAddress.scala similarity index 100% rename from nio/src/main/scala/zio/nio/InterfaceAddress.scala rename to nio/shared/src/main/scala/zio/nio/InterfaceAddress.scala diff --git a/nio/shared/src/main/scala/zio/nio/LongBuffer.scala b/nio/shared/src/main/scala/zio/nio/LongBuffer.scala new file mode 100644 index 00000000..347b3e46 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/LongBuffer.scala @@ -0,0 +1,58 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, LongBuffer => JLongBuffer} + +/** + * A mutable buffer of longs. + */ +final class LongBuffer(protected[nio] val buffer: JLongBuffer) extends Buffer[Long] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Long]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order) + + override def slice(implicit trace: Trace): UIO[LongBuffer] = ZIO.succeed(new LongBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[LongBuffer] = + ZIO.succeed(new LongBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java long buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java long buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JLongBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Long] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Long] = ZIO.succeed(buffer.get(i)) + + override def getChunk(maxLength: Int = Int.MaxValue)(implicit trace: Trace): UIO[Chunk[Long]] = + ZIO.succeed { + val array = Array.ofDim[Long](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + override def put(element: Long)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Long)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Long])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[LongBuffer] = + ZIO.succeed(new LongBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/src/main/scala/zio/nio/MappedByteBuffer.scala b/nio/shared/src/main/scala/zio/nio/MappedByteBuffer.scala similarity index 67% rename from nio/src/main/scala/zio/nio/MappedByteBuffer.scala rename to nio/shared/src/main/scala/zio/nio/MappedByteBuffer.scala index b706fe12..07e31049 100644 --- a/nio/src/main/scala/zio/nio/MappedByteBuffer.scala +++ b/nio/shared/src/main/scala/zio/nio/MappedByteBuffer.scala @@ -1,7 +1,6 @@ package zio.nio -import zio.blocking.Blocking -import zio.{IO, UIO, ZIO} +import zio.{UIO, Trace, ZIO} import java.nio.{MappedByteBuffer => JMappedByteBuffer} @@ -18,15 +17,15 @@ final class MappedByteBuffer private[nio] (override protected[nio] val buffer: J /** * Tells whether or not this buffer's content is resident in physical memory. */ - def isLoaded: UIO[Boolean] = IO.effectTotal(buffer.isLoaded) + def isLoaded(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(buffer.isLoaded) /** * Loads this buffer's content into physical memory. */ - def load: ZIO[Blocking, Nothing, Unit] = ZIO.accessM(_.get.blocking(IO.effectTotal(buffer.load()).unit)) + def load(implicit trace: Trace): ZIO[Any, Nothing, Unit] = ZIO.blocking(ZIO.succeed(buffer.load()).unit) /** * Forces any changes made to this buffer's content to be written to the storage device containing the mapped file. */ - def force: ZIO[Blocking, Nothing, Unit] = ZIO.accessM(_.get.blocking(IO.effectTotal(buffer.force()).unit)) + def force(implicit trace: Trace): ZIO[Any, Nothing, Unit] = ZIO.blocking(ZIO.succeed(buffer.force()).unit) } diff --git a/nio/shared/src/main/scala/zio/nio/NetworkInterface.scala b/nio/shared/src/main/scala/zio/nio/NetworkInterface.scala new file mode 100644 index 00000000..d8d7f85f --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/NetworkInterface.scala @@ -0,0 +1,73 @@ +package zio.nio + +import zio.{IO, Trace, ZIO} + +import java.net.{NetworkInterface => JNetworkInterface, SocketException} +import scala.jdk.CollectionConverters._ + +class NetworkInterface private[nio] (private[nio] val jNetworkInterface: JNetworkInterface) { + + def name: String = jNetworkInterface.getName + + def inetAddresses: List[InetAddress] = jNetworkInterface.getInetAddresses.asScala.map(new InetAddress(_)).toList + + def interfaceAddresses: List[InterfaceAddress] = + jNetworkInterface.getInterfaceAddresses.asScala.map(new InterfaceAddress(_)).toList + + def subInterfaces: Iterator[NetworkInterface] = + jNetworkInterface.getSubInterfaces.asScala.map(new NetworkInterface(_)) + + def parent: NetworkInterface = new NetworkInterface(jNetworkInterface.getParent) + + def index: Int = jNetworkInterface.getIndex + + def displayName: String = jNetworkInterface.getDisplayName + + def isUp(implicit trace: Trace): IO[SocketException, Boolean] = + ZIO.attempt(jNetworkInterface.isUp).refineToOrDie[SocketException] + + def isLoopback(implicit trace: Trace): IO[SocketException, Boolean] = + ZIO.attempt(jNetworkInterface.isLoopback).refineToOrDie[SocketException] + + def isPointToPoint(implicit trace: Trace): IO[SocketException, Boolean] = + ZIO.attempt(jNetworkInterface.isPointToPoint).refineToOrDie[SocketException] + + def supportsMulticast(implicit trace: Trace): IO[SocketException, Boolean] = + ZIO.attempt(jNetworkInterface.supportsMulticast).refineToOrDie[SocketException] + + def hardwareAddress(implicit trace: Trace): IO[SocketException, Array[Byte]] = + ZIO.attempt(jNetworkInterface.getHardwareAddress).refineToOrDie[SocketException] + + def mtu(implicit trace: Trace): IO[SocketException, Int] = + ZIO.attempt(jNetworkInterface.getMTU).refineToOrDie[SocketException] + + def isVirtual: Boolean = jNetworkInterface.isVirtual +} + +object NetworkInterface { + + def byName(name: String)(implicit trace: Trace): IO[SocketException, NetworkInterface] = + ZIO + .attempt(JNetworkInterface.getByName(name)) + .refineToOrDie[SocketException] + .map(new NetworkInterface(_)) + + def byIndex(index: Integer)(implicit trace: Trace): IO[SocketException, NetworkInterface] = + ZIO + .attempt(JNetworkInterface.getByIndex(index)) + .refineToOrDie[SocketException] + .map(new NetworkInterface(_)) + + def byInetAddress(address: InetAddress)(implicit trace: Trace): IO[SocketException, NetworkInterface] = + ZIO + .attempt(JNetworkInterface.getByInetAddress(address.jInetAddress)) + .refineToOrDie[SocketException] + .map(new NetworkInterface(_)) + + def networkInterfaces(implicit trace: Trace): IO[SocketException, Iterator[NetworkInterface]] = + ZIO + .attempt(JNetworkInterface.getNetworkInterfaces.asScala) + .refineToOrDie[SocketException] + .map(_.map(new NetworkInterface(_))) + +} diff --git a/nio/shared/src/main/scala/zio/nio/ShortBuffer.scala b/nio/shared/src/main/scala/zio/nio/ShortBuffer.scala new file mode 100644 index 00000000..c169303d --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/ShortBuffer.scala @@ -0,0 +1,58 @@ +package zio.nio + +import zio.{Chunk, Trace, UIO, ZIO} + +import java.nio.{ByteOrder, ShortBuffer => JShortBuffer} + +/** + * A mutable buffer of shorts. + */ +final class ShortBuffer(protected[nio] val buffer: JShortBuffer) extends Buffer[Short] { + + override protected[nio] def array(implicit trace: Trace): UIO[Array[Short]] = ZIO.succeed(buffer.array()) + + override def order(implicit trace: Trace): UIO[ByteOrder] = ZIO.succeed(buffer.order()) + + override def slice(implicit trace: Trace): UIO[ShortBuffer] = ZIO.succeed(new ShortBuffer(buffer.slice())) + + override def compact(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.compact()).unit + + override def duplicate(implicit trace: Trace): UIO[ShortBuffer] = + ZIO.succeed(new ShortBuffer(buffer.duplicate())) + + /** + * Provides the underlying Java short buffer for use in an effect. + * + * This is useful when using Java APIs that require a Java short buffer to be provided. + * + * @return + * The effect value constructed by `f` using the underlying buffer. + */ + def withJavaBuffer[R, E, A](f: JShortBuffer => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = f(buffer) + + override def get(implicit trace: Trace): UIO[Short] = ZIO.succeed(buffer.get()) + + override def get(i: Int)(implicit trace: Trace): UIO[Short] = ZIO.succeed(buffer.get(i)) + + override def getChunk(maxLength: Int)(implicit trace: Trace): UIO[Chunk[Short]] = + ZIO.succeed { + val array = Array.ofDim[Short](math.min(maxLength, buffer.remaining())) + buffer.get(array) + Chunk.fromArray(array) + } + + override def put(element: Short)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(buffer.put(element)).unit + + override def put(index: Int, element: Short)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(buffer.put(index, element)).unit + + override protected def putChunkAll(chunk: Chunk[Short])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed { + val array = chunk.toArray + buffer.put(array) + }.unit + + override def asReadOnlyBuffer(implicit trace: Trace): UIO[ShortBuffer] = + ZIO.succeed(new ShortBuffer(buffer.asReadOnlyBuffer())) + +} diff --git a/nio/shared/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala b/nio/shared/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala new file mode 100644 index 00000000..9f3dd4e7 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala @@ -0,0 +1,65 @@ +package zio.nio.channels + +import zio._ + +import java.io.IOException +import java.nio.channels.spi.{AsynchronousChannelProvider => JAsynchronousChannelProvider} +import java.nio.channels.{AsynchronousChannelGroup => JAsynchronousChannelGroup} +import java.util.concurrent.{ThreadFactory => JThreadFactory, TimeUnit} +import scala.concurrent.ExecutionContextExecutorService + +object AsynchronousChannelGroup { + + def apply(executor: ExecutionContextExecutorService, initialSize: Int)(implicit + trace: Trace + ): IO[IOException, AsynchronousChannelGroup] = + ZIO + .attempt( + new AsynchronousChannelGroup( + JAsynchronousChannelGroup.withCachedThreadPool(executor, initialSize) + ) + ) + .refineToOrDie[IOException] + + def apply( + threadsNo: Int, + threadsFactory: JThreadFactory + )(implicit trace: Trace): IO[IOException, AsynchronousChannelGroup] = + ZIO + .attempt( + new AsynchronousChannelGroup( + JAsynchronousChannelGroup.withFixedThreadPool(threadsNo, threadsFactory) + ) + ) + .refineToOrDie[IOException] + + def apply( + executor: ExecutionContextExecutorService + )(implicit trace: Trace): IO[IOException, AsynchronousChannelGroup] = + ZIO + .attempt( + new AsynchronousChannelGroup(JAsynchronousChannelGroup.withThreadPool(executor)) + ) + .refineToOrDie[IOException] + +} + +final class AsynchronousChannelGroup(val channelGroup: JAsynchronousChannelGroup) { + + def awaitTermination(timeout: Duration)(implicit trace: Trace): IO[InterruptedException, Boolean] = + ZIO + .attempt(channelGroup.awaitTermination(timeout.asJava.toMillis, TimeUnit.MILLISECONDS)) + .refineToOrDie[InterruptedException] + + def isShutdown(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(channelGroup.isShutdown) + + def isTerminated(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(channelGroup.isTerminated) + + def provider(implicit trace: Trace): UIO[JAsynchronousChannelProvider] = ZIO.succeed(channelGroup.provider()) + + def shutdown(implicit trace: Trace): UIO[Unit] = ZIO.succeed(channelGroup.shutdown()) + + def shutdownNow(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channelGroup.shutdownNow()).refineToOrDie[IOException] + +} diff --git a/nio/src/main/scala/zio/nio/channels/Channel.scala b/nio/shared/src/main/scala/zio/nio/channels/Channel.scala similarity index 72% rename from nio/src/main/scala/zio/nio/channels/Channel.scala rename to nio/shared/src/main/scala/zio/nio/channels/Channel.scala index 22b6e780..52f31a15 100644 --- a/nio/src/main/scala/zio/nio/channels/Channel.scala +++ b/nio/shared/src/main/scala/zio/nio/channels/Channel.scala @@ -1,8 +1,8 @@ package zio.nio.channels - -import zio.blocking.{Blocking, blocking} +import zio.ZIO.blocking import zio.nio.IOCloseable -import zio.{IO, UIO, ZIO} + +import zio.{IO, Trace, UIO, ZIO} import java.io.IOException import java.nio.channels.{Channel => JChannel} @@ -14,12 +14,13 @@ trait Channel extends IOCloseable { /** * Closes this channel. */ - final def close: IO[IOException, Unit] = IO.effect(channel.close()).refineToOrDie[IOException] + final def close(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.close()).refineToOrDie[IOException] /** * Tells whether or not this channel is open. */ - final def isOpen: UIO[Boolean] = IO.effectTotal(channel.isOpen) + final def isOpen(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(channel.isOpen) } /** @@ -32,7 +33,9 @@ trait BlockingChannel extends Channel { */ type BlockingOps - final protected def nioBlocking[R, E, A](zioEffect: ZIO[R, E, A]): ZIO[R with Blocking, E, A] = + final protected def nioBlocking[R, E, A](zioEffect: ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R with Any, E, A] = blocking(zioEffect).fork.flatMap(_.join).onInterrupt(close.ignore) /** @@ -44,6 +47,8 @@ trait BlockingChannel extends Channel { * Given a `BlockingOps` argument appropriate for this channel type, produces an effect value containing blocking * operations. */ - def useBlocking[R, E >: IOException, A](f: BlockingOps => ZIO[R, E, A]): ZIO[R with Blocking, E, A] + def flatMapBlocking[R, E >: IOException, A](f: BlockingOps => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R with Any, E, A] } diff --git a/nio/src/main/scala/zio/nio/channels/FileChannel.scala b/nio/shared/src/main/scala/zio/nio/channels/FileChannel.scala similarity index 78% rename from nio/src/main/scala/zio/nio/channels/FileChannel.scala rename to nio/shared/src/main/scala/zio/nio/channels/FileChannel.scala index ceb7820d..5083742d 100644 --- a/nio/src/main/scala/zio/nio/channels/FileChannel.scala +++ b/nio/shared/src/main/scala/zio/nio/channels/FileChannel.scala @@ -1,16 +1,15 @@ package zio.nio.channels -import com.github.ghik.silencer.silent -import zio.blocking.Blocking import zio.nio.file.Path import zio.nio.{ByteBuffer, IOCloseableManagement, MappedByteBuffer} -import zio.{IO, Managed, ZIO} + +import zio.{IO, Scope, Trace, ZIO} import java.io.IOException import java.nio.channels.{FileChannel => JFileChannel} import java.nio.file.OpenOption import java.nio.file.attribute.FileAttribute -import scala.collection.JavaConverters._ +import scala.jdk.CollectionConverters._ /** * A channel for reading, writing, mapping, and manipulating a file. @@ -38,7 +37,8 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param size * The new size, must be >= 0 */ - def truncate(size: Long): IO[IOException, Unit] = IO.effect(channel.truncate(size)).unit.refineToOrDie[IOException] + def truncate(size: Long)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.truncate(size)).unit.refineToOrDie[IOException] /** * Forces any updates to this channel's file to be written to the storage device that contains it. @@ -47,7 +47,8 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * If true then this method is required to force changes to both the file's content and metadata to be written to * storage; otherwise, it need only force content changes to be written */ - def force(metadata: Boolean): IO[IOException, Unit] = IO.effect(channel.force(metadata)).refineToOrDie[IOException] + def force(metadata: Boolean)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.force(metadata)).refineToOrDie[IOException] /** * Transfers bytes from this channel's file to the given writable byte channel. @@ -59,8 +60,10 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param target * The target channel */ - def transferTo(position: Long, count: Long, target: GatheringByteOps): IO[IOException, Long] = - IO.effect(channel.transferTo(position, count, target.channel)).refineToOrDie[IOException] + def transferTo(position: Long, count: Long, target: GatheringByteOps)(implicit + trace: Trace + ): IO[IOException, Long] = + ZIO.attempt(channel.transferTo(position, count, target.channel)).refineToOrDie[IOException] /** * Transfers bytes into this channel's file from the given readable byte channel. @@ -72,8 +75,10 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param count * The maximum number of bytes to be transferred, must be >= 0 */ - def transferFrom(src: ScatteringByteOps, position: Long, count: Long): IO[IOException, Long] = - IO.effect(channel.transferFrom(src.channel, position, count)).refineToOrDie[IOException] + def transferFrom(src: ScatteringByteOps, position: Long, count: Long)(implicit + trace: Trace + ): IO[IOException, Long] = + ZIO.attempt(channel.transferFrom(src.channel, position, count)).refineToOrDie[IOException] /** * Reads a sequence of bytes from this channel into the given buffer, starting at the given file position. This @@ -86,9 +91,9 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param position * The file position at which the transfer is to begin, must be >= 0 */ - def read(dst: ByteBuffer, position: Long): IO[IOException, Int] = + def read(dst: ByteBuffer, position: Long)(implicit trace: Trace): IO[IOException, Int] = dst - .withJavaBuffer[Any, Throwable, Int](buffer => IO.effect(channel.read(buffer, position))) + .withJavaBuffer[Any, Throwable, Int](buffer => ZIO.attempt(channel.read(buffer, position))) .refineToOrDie[IOException] /** @@ -105,9 +110,9 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * The file position at which the transfer is to begin, must be >= 0 * @return */ - def write(src: ByteBuffer, position: Long): IO[IOException, Int] = + def write(src: ByteBuffer, position: Long)(implicit trace: Trace): IO[IOException, Int] = src - .withJavaBuffer[Any, Throwable, Int](buffer => IO.effect(channel.write(buffer, position))) + .withJavaBuffer[Any, Throwable, Int](buffer => ZIO.attempt(channel.write(buffer, position))) .refineToOrDie[IOException] /** @@ -129,8 +134,11 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param size * The size of the region to be mapped, must be >= 0 and <= `Int.MaxValue` */ - def map(mode: JFileChannel.MapMode, position: Long, size: Long): IO[IOException, MappedByteBuffer] = - IO.effect(new MappedByteBuffer(channel.map(mode, position, size))) + def map(mode: JFileChannel.MapMode, position: Long, size: Long)(implicit + trace: Trace + ): IO[IOException, MappedByteBuffer] = + ZIO + .attempt(new MappedByteBuffer(channel.map(mode, position, size))) .refineToOrDie[IOException] /** @@ -149,18 +157,21 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) position: Long = 0L, size: Long = Long.MaxValue, shared: Boolean = false - ): IO[IOException, FileLock] = - IO.effect(new FileLock(channel.lock(position, size, shared))).refineToOrDie[IOException] + )(implicit trace: Trace): IO[IOException, FileLock] = + ZIO.attempt(new FileLock(channel.lock(position, size, shared))).refineToOrDie[IOException] } - override def useBlocking[R, E, A](f: BlockingFileOps => ZIO[R, E, A]): ZIO[R with Blocking, E, A] = + override def flatMapBlocking[R, E, A](f: BlockingFileOps => ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R with Any, E, A] = nioBlocking(f(new BlockingOps)) /** * Returns the current value of this channel's position. */ - def position: IO[IOException, Long] = IO.effect(channel.position()).refineToOrDie[IOException] + def position(implicit trace: Trace): IO[IOException, Long] = + ZIO.attempt(channel.position()).refineToOrDie[IOException] /** * Sets this channel's position. Setting the position to a value that is greater than the file's current size is legal @@ -172,13 +183,13 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) * @param newPosition * The new position, must be >= 0 */ - def position(newPosition: Long): IO[IOException, Unit] = - IO.effect(channel.position(newPosition)).unit.refineToOrDie[IOException] + def position(newPosition: Long)(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(channel.position(newPosition)).unit.refineToOrDie[IOException] /** * Returns the current size of this channel's file. */ - def size: IO[IOException, Long] = IO.effect(channel.size()).refineToOrDie[IOException] + def size(implicit trace: Trace): IO[IOException, Long] = ZIO.attempt(channel.size()).refineToOrDie[IOException] /** * Attempts to acquire a lock on the given region of this channel's file. This method does not block. An invocation @@ -198,8 +209,8 @@ final class FileChannel private[channels] (protected val channel: JFileChannel) position: Long = 0L, size: Long = Long.MaxValue, shared: Boolean = false - ): IO[IOException, Option[FileLock]] = - ZIO.effect(Option(channel.tryLock(position, size, shared)).map(new FileLock(_))).refineToOrDie[IOException] + )(implicit trace: Trace): IO[IOException, Option[FileLock]] = + ZIO.attempt(Option(channel.tryLock(position, size, shared)).map(new FileLock(_))).refineToOrDie[IOException] } @@ -215,15 +226,15 @@ object FileChannel { * @param attrs * An optional list of file attributes to set atomically when creating the file */ - @silent("object JavaConverters in package collection is deprecated") def open( path: Path, options: Set[_ <: OpenOption], attrs: FileAttribute[_]* - ): Managed[IOException, FileChannel] = - IO.effect(new FileChannel(JFileChannel.open(path.javaPath, options.asJava, attrs: _*))) + )(implicit trace: Trace): ZIO[Scope, IOException, FileChannel] = + ZIO + .attempt(new FileChannel(JFileChannel.open(path.javaPath, options.asJava, attrs: _*))) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped /** * Opens or creates a file, returning a file channel to access the file. @@ -233,10 +244,11 @@ object FileChannel { * @param options * Specifies how the file is opened */ - def open(path: Path, options: OpenOption*): Managed[IOException, FileChannel] = - IO.effect(new FileChannel(JFileChannel.open(path.javaPath, options: _*))) + def open(path: Path, options: OpenOption*)(implicit trace: Trace): ZIO[Scope, IOException, FileChannel] = + ZIO + .attempt(new FileChannel(JFileChannel.open(path.javaPath, options: _*))) .refineToOrDie[IOException] - .toNioManaged + .toNioScoped def fromJava(javaFileChannel: JFileChannel): FileChannel = new FileChannel(javaFileChannel) diff --git a/nio/src/main/scala/zio/nio/channels/FileLock.scala b/nio/shared/src/main/scala/zio/nio/channels/FileLock.scala similarity index 90% rename from nio/src/main/scala/zio/nio/channels/FileLock.scala rename to nio/shared/src/main/scala/zio/nio/channels/FileLock.scala index 48dfac5b..15d18190 100644 --- a/nio/src/main/scala/zio/nio/channels/FileLock.scala +++ b/nio/shared/src/main/scala/zio/nio/channels/FileLock.scala @@ -1,6 +1,6 @@ package zio.nio.channels -import zio.{IO, UIO} +import zio.{IO, Trace, UIO, ZIO} import java.io.IOException import java.nio.channels.{FileLock => JFileLock} @@ -61,9 +61,10 @@ final class FileLock private[channels] (javaLock: JFileLock) { * Tells whether or not this lock is valid. A lock object remains valid until it is released or the associated file * channel is closed, whichever comes first. */ - def isValid: UIO[Boolean] = UIO.effectTotal(javaLock.isValid) + def isValid(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(javaLock.isValid) - def release: IO[IOException, Unit] = IO.effect(javaLock.release()).refineToOrDie[IOException] + def release(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(javaLock.release()).refineToOrDie[IOException] } object FileLock { diff --git a/nio/src/main/scala/zio/nio/channels/GatheringByteOps.scala b/nio/shared/src/main/scala/zio/nio/channels/GatheringByteOps.scala similarity index 63% rename from nio/src/main/scala/zio/nio/channels/GatheringByteOps.scala rename to nio/shared/src/main/scala/zio/nio/channels/GatheringByteOps.scala index 563f699f..c807fbf4 100644 --- a/nio/src/main/scala/zio/nio/channels/GatheringByteOps.scala +++ b/nio/shared/src/main/scala/zio/nio/channels/GatheringByteOps.scala @@ -1,8 +1,8 @@ package zio.nio.channels import zio._ -import zio.clock.Clock import zio.nio.{Buffer, ByteBuffer} + import zio.stream.{ZSink, ZStream} import java.io.IOException @@ -18,32 +18,32 @@ trait GatheringByteOps { protected[channels] def channel: JGatheringByteChannel - final def write(srcs: List[ByteBuffer]): IO[IOException, Long] = - IO.effect(channel.write(unwrap(srcs))).refineToOrDie[IOException] + final def write(srcs: List[ByteBuffer])(implicit trace: Trace): IO[IOException, Long] = + ZIO.attempt(channel.write(unwrap(srcs))).refineToOrDie[IOException] - final def write(src: ByteBuffer): IO[IOException, Int] = - IO.effect(channel.write(src.buffer)).refineToOrDie[IOException] + final def write(src: ByteBuffer)(implicit trace: Trace): IO[IOException, Int] = + ZIO.attempt(channel.write(src.buffer)).refineToOrDie[IOException] /** * Writes a list of chunks, in order. * * Multiple writes may be performed in order to write all the chunks. */ - final def writeChunks(srcs: List[Chunk[Byte]]): IO[IOException, Unit] = + final def writeChunks(srcs: List[Chunk[Byte]])(implicit trace: Trace): IO[IOException, Unit] = for { - bs <- IO.foreach(srcs)(Buffer.byte) + bs <- ZIO.foreach(srcs)(Buffer.byte) _ <- { // Handle partial writes by dropping buffers where `hasRemaining` returns false, // meaning they've been completely written - def go(buffers: List[ByteBuffer]): IO[IOException, Unit] = + def go(buffers: List[ByteBuffer])(implicit trace: Trace): IO[IOException, Unit] = for { _ <- write(buffers) - pairs <- IO.foreach(buffers)(b => b.hasRemaining.map(_ -> b)) - r <- { + pairs <- ZIO.foreach(buffers)(b => b.hasRemaining.map(_ -> b)) + _ <- { val remaining = pairs.dropWhile(!_._1).map(_._2) go(remaining).unless(remaining.isEmpty) } - } yield r + } yield () go(bs) } } yield () @@ -53,7 +53,9 @@ trait GatheringByteOps { * * Multiple writes may be performed to write the entire chunk. */ - final def writeChunk(src: Chunk[Byte]): IO[IOException, Unit] = writeChunks(List(src)) + final def writeChunk(src: Chunk[Byte])(implicit trace: Trace): IO[IOException, Unit] = writeChunks(List(src)) + + def sink()(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = sink(Buffer.byte(5000)) /** * A sink that will write all the bytes it receives to this channel. The sink's result is the number of bytes written. @@ -66,20 +68,21 @@ trait GatheringByteOps { * default a heap buffer is used, but a direct buffer will usually perform better. */ def sink( - bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000) - ): ZSink[Clock, IOException, Byte, Byte, Long] = - ZSink { + bufferConstruct: UIO[ByteBuffer] + )(implicit trace: Trace): ZSink[Any, IOException, Byte, Byte, Long] = + ZSink.fromPush { for { - buffer <- bufferConstruct.toManaged_ - countRef <- Ref.makeManaged(0L) + buffer <- bufferConstruct + countRef <- Ref.make(0L) } yield (_: Option[Chunk[Byte]]).map { chunk => - def doWrite(total: Int, c: Chunk[Byte]): ZIO[Clock, IOException, Int] = { + def doWrite(total: Int, c: Chunk[Byte])(implicit trace: Trace): ZIO[Any, IOException, Int] = { val x = for { remaining <- buffer.putChunk(c) _ <- buffer.flip - count <- ZStream - .repeatEffectWith(write(buffer), Schedule.recurWhileM(Function.const(buffer.hasRemaining))) - .runSum + count <- + ZStream + .repeatZIOWithSchedule(write(buffer), Schedule.recurWhileZIO(Function.const(buffer.hasRemaining))) + .runSum _ <- buffer.clear } yield (count + total, remaining) // can't safely recurse in for expression @@ -89,14 +92,14 @@ trait GatheringByteOps { } } - doWrite(0, chunk).foldM( - e => buffer.getChunk().flatMap(ZSink.Push.fail(e, _)), + doWrite(0, chunk).foldZIO( + e => buffer.getChunk().flatMap(chunk => ZIO.fail((Left(e), chunk))), count => countRef.update(_ + count.toLong) ) } .getOrElse( countRef.get.flatMap[Any, (Either[IOException, Long], Chunk[Byte]), Unit](count => - ZSink.Push.emit(count, Chunk.empty) + ZIO.fail((Right(count), Chunk.empty)) ) ) } diff --git a/nio/src/main/scala/zio/nio/channels/ScatteringByteOps.scala b/nio/shared/src/main/scala/zio/nio/channels/ScatteringByteOps.scala similarity index 72% rename from nio/src/main/scala/zio/nio/channels/ScatteringByteOps.scala rename to nio/shared/src/main/scala/zio/nio/channels/ScatteringByteOps.scala index 26ba06b2..e4ab8b8b 100644 --- a/nio/src/main/scala/zio/nio/channels/ScatteringByteOps.scala +++ b/nio/shared/src/main/scala/zio/nio/channels/ScatteringByteOps.scala @@ -3,7 +3,7 @@ package zio.nio package channels import zio.stream.{Stream, ZStream} -import zio.{Chunk, IO, UIO} +import zio.{Chunk, IO, Trace, UIO, ZIO} import java.io.{EOFException, IOException} import java.nio.channels.{ScatteringByteChannel => JScatteringByteChannel} @@ -26,8 +26,8 @@ trait ScatteringByteOps { * @return * The number of bytes read in total, possibly 0 */ - final def read(dsts: Seq[ByteBuffer]): IO[IOException, Long] = - IO.effect(channel.read(unwrap(dsts))).refineToOrDie[IOException].flatMap(eofCheck) + final def read(dsts: Seq[ByteBuffer])(implicit trace: Trace): IO[IOException, Long] = + ZIO.attempt(channel.read(unwrap(dsts))).refineToOrDie[IOException].flatMap(eofCheck) /** * Reads a sequence of bytes from this channel into the given buffer. @@ -37,8 +37,8 @@ trait ScatteringByteOps { * @return * The number of bytes read, possibly 0 */ - final def read(dst: ByteBuffer): IO[IOException, Int] = - IO.effect(channel.read(dst.buffer)).refineToOrDie[IOException].flatMap(eofCheck) + final def read(dst: ByteBuffer)(implicit trace: Trace): IO[IOException, Int] = + ZIO.attempt(channel.read(dst.buffer)).refineToOrDie[IOException].flatMap(eofCheck) /** * Reads a chunk of bytes. @@ -50,7 +50,7 @@ trait ScatteringByteOps { * @return * The bytes read, between 0 and `capacity` in size, inclusive */ - final def readChunk(capacity: Int): IO[IOException, Chunk[Byte]] = + final def readChunk(capacity: Int)(implicit trace: Trace): IO[IOException, Chunk[Byte]] = for { buffer <- Buffer.byte(capacity) _ <- read(buffer) @@ -69,13 +69,15 @@ trait ScatteringByteOps { * A list with one `Chunk` per input size. Some chunks may be less than the requested size if the channel does not * have enough data */ - final def readChunks(capacities: Seq[Int]): IO[IOException, List[Chunk[Byte]]] = + final def readChunks(capacities: Seq[Int])(implicit trace: Trace): IO[IOException, List[Chunk[Byte]]] = for { - buffers <- IO.foreach(capacities)(Buffer.byte) + buffers <- ZIO.foreach(capacities)(Buffer.byte) _ <- read(buffers) - chunks <- IO.foreach(buffers.init)(buf => buf.flip *> buf.getChunk()) + chunks <- ZIO.foreach(buffers.init)(buf => buf.flip *> buf.getChunk()) } yield chunks.toList + def stream()(implicit trace: Trace): Stream[IOException, Byte] = stream(Buffer.byte(5000)) + /** * A `ZStream` that reads from this channel. '''Note:''' This method does not work well with a channel in non-blocking * mode, as it will busy-wait whenever the channel is not ready for reads. The returned stream should be run within @@ -88,23 +90,24 @@ trait ScatteringByteOps { * default a heap buffer is used, but a direct buffer will usually perform better. */ def stream( - bufferConstruct: UIO[ByteBuffer] = Buffer.byte(5000) - ): Stream[IOException, Byte] = - ZStream { - bufferConstruct.toManaged_.map { buffer => + bufferConstruct: UIO[ByteBuffer] + )(implicit trace: Trace): Stream[IOException, Byte] = + ZStream.unwrap { + bufferConstruct.map { buffer => val doRead = for { _ <- read(buffer) _ <- buffer.flip chunk <- buffer.getChunk() _ <- buffer.clear } yield chunk - doRead.mapError { - case _: EOFException => None - case e => Some(e) - } + ZStream.repeatZIOChunkOption( + doRead.mapError { + case _: EOFException => None + case e => Some(e) + } + ) } } - } object ScatteringByteOps { diff --git a/nio/src/main/scala/zio/nio/charset/AutoDetect.scala b/nio/shared/src/main/scala/zio/nio/charset/AutoDetect.scala similarity index 100% rename from nio/src/main/scala/zio/nio/charset/AutoDetect.scala rename to nio/shared/src/main/scala/zio/nio/charset/AutoDetect.scala diff --git a/nio/src/main/scala/zio/nio/charset/Charset.scala b/nio/shared/src/main/scala/zio/nio/charset/Charset.scala similarity index 80% rename from nio/src/main/scala/zio/nio/charset/Charset.scala rename to nio/shared/src/main/scala/zio/nio/charset/Charset.scala index 0aeb9e0d..dd00d3cb 100644 --- a/nio/src/main/scala/zio/nio/charset/Charset.scala +++ b/nio/shared/src/main/scala/zio/nio/charset/Charset.scala @@ -2,16 +2,13 @@ package zio package nio package charset -import com.github.ghik.silencer.silent - import java.nio.charset.IllegalCharsetNameException import java.nio.{charset => j} import java.{util => ju} -import scala.collection.JavaConverters._ +import scala.jdk.CollectionConverters._ final class Charset private (val javaCharset: j.Charset) extends Ordered[Charset] { - @silent("object JavaConverters in package collection is deprecated") def aliases: Set[String] = javaCharset.aliases().asScala.toSet def canEncode: Boolean = javaCharset.canEncode @@ -20,15 +17,15 @@ final class Charset private (val javaCharset: j.Charset) extends Ordered[Charset def contains(cs: Charset): Boolean = javaCharset.contains(cs.javaCharset) - def decode(byteBuffer: ByteBuffer): UIO[CharBuffer] = - byteBuffer.withJavaBuffer(jBuf => UIO.effectTotal(Buffer.charFromJava(javaCharset.decode(jBuf)))) + def decode(byteBuffer: ByteBuffer)(implicit trace: Trace): UIO[CharBuffer] = + byteBuffer.withJavaBuffer(jBuf => ZIO.succeed(Buffer.charFromJava(javaCharset.decode(jBuf)))) def displayName: String = javaCharset.displayName() def displayName(locale: ju.Locale): String = javaCharset.displayName(locale) - def encode(charBuffer: CharBuffer): UIO[ByteBuffer] = - charBuffer.withJavaBuffer(jBuf => UIO.effectTotal(Buffer.byteFromJava(javaCharset.encode(jBuf)))) + def encode(charBuffer: CharBuffer)(implicit trace: Trace): UIO[ByteBuffer] = + charBuffer.withJavaBuffer(jBuf => ZIO.succeed(Buffer.byteFromJava(javaCharset.encode(jBuf)))) override def equals(other: Any): Boolean = other match { @@ -48,28 +45,28 @@ final class Charset private (val javaCharset: j.Charset) extends Ordered[Charset override def toString: String = javaCharset.toString - def encodeChunk(chunk: Chunk[Char]): UIO[Chunk[Byte]] = + def encodeChunk(chunk: Chunk[Char])(implicit trace: Trace): UIO[Chunk[Byte]] = for { charBuf <- Buffer.char(chunk) byteBuf <- encode(charBuf) chunk <- byteBuf.getChunk() } yield chunk - def encodeString(s: CharSequence): UIO[Chunk[Byte]] = + def encodeString(s: CharSequence)(implicit trace: Trace): UIO[Chunk[Byte]] = for { charBuf <- Buffer.char(s) byteBuf <- encode(charBuf) chunk <- byteBuf.getChunk() } yield chunk - def decodeChunk(chunk: Chunk[Byte]): UIO[Chunk[Char]] = + def decodeChunk(chunk: Chunk[Byte])(implicit trace: Trace): UIO[Chunk[Char]] = for { byteBuf <- Buffer.byte(chunk) charBuf <- decode(byteBuf) chunk <- charBuf.getChunk() } yield chunk - def decodeString(chunk: Chunk[Byte]): UIO[String] = + def decodeString(chunk: Chunk[Byte])(implicit trace: Trace): UIO[String] = for { byteBuf <- Buffer.byte(chunk) charBuf <- decode(byteBuf) @@ -82,9 +79,14 @@ object Charset { def fromJava(javaCharset: j.Charset): Charset = new Charset(javaCharset) - @silent("deprecated") val availableCharsets: Map[String, Charset] = - j.Charset.availableCharsets().asScala.mapValues(new Charset(_)).toMap + j.Charset + .availableCharsets() + .asScala + .map { case (s, charset) => + (s, new Charset(charset)) + } + .toMap val defaultCharset: Charset = fromJava(j.Charset.defaultCharset()) diff --git a/nio/src/main/scala/zio/nio/charset/CharsetDecoder.scala b/nio/shared/src/main/scala/zio/nio/charset/CharsetDecoder.scala similarity index 60% rename from nio/src/main/scala/zio/nio/charset/CharsetDecoder.scala rename to nio/shared/src/main/scala/zio/nio/charset/CharsetDecoder.scala index 8cf4bc3d..03458d96 100644 --- a/nio/src/main/scala/zio/nio/charset/CharsetDecoder.scala +++ b/nio/shared/src/main/scala/zio/nio/charset/CharsetDecoder.scala @@ -2,7 +2,7 @@ package zio package nio package charset -import zio.stream.{Transducer, ZTransducer} +import zio.stream.{ZChannel, ZPipeline} import java.nio.charset.{MalformedInputException, UnmappableCharacterException} import java.nio.{charset => j} @@ -23,25 +23,25 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A def charset: Charset = Charset.fromJava(javaDecoder.charset()) - def decode(in: ByteBuffer): IO[j.CharacterCodingException, CharBuffer] = - in.withJavaBuffer[Any, Throwable, CharBuffer](jBuf => IO.effect(Buffer.charFromJava(javaDecoder.decode(jBuf)))) + def decode(in: ByteBuffer)(implicit trace: Trace): IO[j.CharacterCodingException, CharBuffer] = + in.withJavaBuffer[Any, Throwable, CharBuffer](jBuf => ZIO.attempt(Buffer.charFromJava(javaDecoder.decode(jBuf)))) .refineToOrDie[j.CharacterCodingException] def decode( in: ByteBuffer, out: CharBuffer, endOfInput: Boolean - ): UIO[CoderResult] = + )(implicit trace: Trace): UIO[CoderResult] = in.withJavaBuffer { jIn => out.withJavaBuffer { jOut => - IO.effectTotal( + ZIO.succeed( CoderResult.fromJava(javaDecoder.decode(jIn, jOut, endOfInput)) ) } } - def autoDetect: UIO[AutoDetect] = - UIO.effectTotal { + def autoDetect(implicit trace: Trace): UIO[AutoDetect] = + ZIO.succeed { if (javaDecoder.isAutoDetecting) if (javaDecoder.isCharsetDetected) AutoDetect.Detected(Charset.fromJava(javaDecoder.detectedCharset())) @@ -51,29 +51,32 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A AutoDetect.NotSupported } - def flush(out: CharBuffer): UIO[CoderResult] = - out.withJavaBuffer(jOut => UIO.effectTotal(CoderResult.fromJava(javaDecoder.flush(jOut)))) + def flush(out: CharBuffer)(implicit trace: Trace): UIO[CoderResult] = + out.withJavaBuffer(jOut => ZIO.succeed(CoderResult.fromJava(javaDecoder.flush(jOut)))) - def malformedInputAction: UIO[j.CodingErrorAction] = UIO.effectTotal(javaDecoder.malformedInputAction()) + def malformedInputAction(implicit trace: Trace): UIO[j.CodingErrorAction] = + ZIO.succeed(javaDecoder.malformedInputAction()) - def onMalformedInput(errorAction: j.CodingErrorAction): UIO[Unit] = - UIO.effectTotal(javaDecoder.onMalformedInput(errorAction)).unit + def onMalformedInput(errorAction: j.CodingErrorAction)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaDecoder.onMalformedInput(errorAction)).unit - def unmappableCharacterAction: UIO[j.CodingErrorAction] = UIO.effectTotal(javaDecoder.unmappableCharacterAction()) + def unmappableCharacterAction(implicit trace: Trace): UIO[j.CodingErrorAction] = + ZIO.succeed(javaDecoder.unmappableCharacterAction()) - def onUnmappableCharacter(errorAction: j.CodingErrorAction): UIO[Unit] = - UIO.effectTotal(javaDecoder.onUnmappableCharacter(errorAction)).unit + def onUnmappableCharacter(errorAction: j.CodingErrorAction)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaDecoder.onUnmappableCharacter(errorAction)).unit def maxCharsPerByte: Float = javaDecoder.maxCharsPerByte() - def replacement: UIO[String] = UIO.effectTotal(javaDecoder.replacement()) + def replacement(implicit trace: Trace): UIO[String] = ZIO.succeed(javaDecoder.replacement()) - def replaceWith(replacement: String): UIO[Unit] = UIO.effectTotal(javaDecoder.replaceWith(replacement)).unit + def replaceWith(replacement: String)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaDecoder.replaceWith(replacement)).unit /** * Resets this decoder, clearing any internal state. */ - def reset: UIO[Unit] = UIO.effectTotal(javaDecoder.reset()).unit + def reset(implicit trace: Trace): UIO[Unit] = ZIO.succeed(javaDecoder.reset()).unit /** * Decodes a stream of bytes into characters according to this character set's encoding. @@ -83,15 +86,19 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A * @param bufSize * The size of the internal buffer used for encoding. Must be at least 50. */ - def transducer(bufSize: Int = 5000): Transducer[j.CharacterCodingException, Byte, Char] = { - val push: Managed[Nothing, Option[Chunk[Byte]] => IO[j.CharacterCodingException, Chunk[Char]]] = { + def transducer( + bufSize: Int = 5000 + )(implicit trace: Trace): ZPipeline[Any, j.CharacterCodingException, Byte, Char] = { + def push(implicit + trace: Trace + ): ZIO[Any, Nothing, Option[Chunk[Byte]] => IO[j.CharacterCodingException, Chunk[Char]]] = for { - _ <- reset.toManaged_ - byteBuffer <- Buffer.byte(bufSize).toManaged_ - charBuffer <- Buffer.char((bufSize.toFloat * this.averageCharsPerByte).round).toManaged_ + _ <- reset + byteBuffer <- Buffer.byte(bufSize) + charBuffer <- Buffer.char((bufSize.toFloat * this.averageCharsPerByte).round) } yield { - def handleCoderResult(coderResult: CoderResult) = + def handleCoderResult(coderResult: CoderResult)(implicit trace: Trace) = coderResult match { case CoderResult.Underflow | CoderResult.Overflow => byteBuffer.compact *> @@ -99,13 +106,15 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A charBuffer.getChunk() <* charBuffer.clear case CoderResult.Malformed(length) => - IO.fail(new MalformedInputException(length)) + ZIO.fail(new MalformedInputException(length)) case CoderResult.Unmappable(length) => - IO.fail(new UnmappableCharacterException(length)) + ZIO.fail(new UnmappableCharacterException(length)) } (_: Option[Chunk[Byte]]).map { inChunk => - def decodeChunk(inBytes: Chunk[Byte]): IO[j.CharacterCodingException, Chunk[Char]] = + def decodeChunk(inBytes: Chunk[Byte])(implicit + trace: Trace + ): IO[j.CharacterCodingException, Chunk[Char]] = for { bufRemaining <- byteBuffer.remaining (decodeBytes, remainingBytes) = { @@ -122,12 +131,12 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A endOfInput = false ) decodedChars <- handleCoderResult(result) - remainderChars <- if (remainingBytes.isEmpty) IO.succeed(Chunk.empty) else decodeChunk(remainingBytes) + remainderChars <- if (remainingBytes.isEmpty) ZIO.succeed(Chunk.empty) else decodeChunk(remainingBytes) } yield decodedChars ++ remainderChars decodeChunk(inChunk) }.getOrElse { - def endOfInput: IO[j.CharacterCodingException, Chunk[Char]] = + def endOfInput(implicit trace: Trace): IO[j.CharacterCodingException, Chunk[Char]] = for { result <- decode( byteBuffer, @@ -135,25 +144,26 @@ final class CharsetDecoder private (val javaDecoder: j.CharsetDecoder) extends A endOfInput = true ) decodedChars <- handleCoderResult(result) - remainderChars <- if (result == CoderResult.Overflow) endOfInput else IO.succeed(Chunk.empty) + remainderChars <- if (result == CoderResult.Overflow) endOfInput else ZIO.succeed(Chunk.empty) } yield decodedChars ++ remainderChars byteBuffer.flip *> endOfInput.flatMap { decodedChars => - def flushRemaining: IO[j.CharacterCodingException, Chunk[Char]] = + def flushRemaining(implicit trace: Trace): IO[j.CharacterCodingException, Chunk[Char]] = for { result <- flush(charBuffer) decodedChars <- handleCoderResult(result) - remainderChars <- if (result == CoderResult.Overflow) flushRemaining else IO.succeed(Chunk.empty) + remainderChars <- if (result == CoderResult.Overflow) flushRemaining else ZIO.succeed(Chunk.empty) } yield decodedChars ++ remainderChars flushRemaining.map(decodedChars ++ _) } <* byteBuffer.clear <* charBuffer.clear } } - } if (bufSize < 50) - ZTransducer.die(new IllegalArgumentException(s"Buffer size is $bufSize, must be >= 50")) + ZPipeline.fromChannel( + ZChannel.fromZIO(ZIO.die(new IllegalArgumentException(s"Buffer size is $bufSize, must be >= 50"))) + ) else - ZTransducer(push) + ZPipeline.fromPush(push) } } diff --git a/nio/src/main/scala/zio/nio/charset/CharsetEncoder.scala b/nio/shared/src/main/scala/zio/nio/charset/CharsetEncoder.scala similarity index 57% rename from nio/src/main/scala/zio/nio/charset/CharsetEncoder.scala rename to nio/shared/src/main/scala/zio/nio/charset/CharsetEncoder.scala index f6d1616d..903e2e82 100644 --- a/nio/src/main/scala/zio/nio/charset/CharsetEncoder.scala +++ b/nio/shared/src/main/scala/zio/nio/charset/CharsetEncoder.scala @@ -2,7 +2,7 @@ package zio package nio package charset -import zio.stream.{Transducer, ZTransducer} +import zio.stream.{ZChannel, ZPipeline} import java.nio.charset.{MalformedInputException, UnmappableCharacterException} import java.nio.{charset => j} @@ -23,39 +23,42 @@ final class CharsetEncoder private (val javaEncoder: j.CharsetEncoder) extends A def charset: Charset = Charset.fromJava(javaEncoder.charset()) - def encode(in: CharBuffer): IO[j.CharacterCodingException, ByteBuffer] = - in.withJavaBuffer[Any, Throwable, ByteBuffer](jBuf => IO.effect(Buffer.byteFromJava(javaEncoder.encode(jBuf)))) + def encode(in: CharBuffer)(implicit trace: Trace): IO[j.CharacterCodingException, ByteBuffer] = + in.withJavaBuffer[Any, Throwable, ByteBuffer](jBuf => ZIO.attempt(Buffer.byteFromJava(javaEncoder.encode(jBuf)))) .refineToOrDie[j.CharacterCodingException] - def encode(in: CharBuffer, out: ByteBuffer, endOfInput: Boolean): UIO[CoderResult] = + def encode(in: CharBuffer, out: ByteBuffer, endOfInput: Boolean)(implicit trace: Trace): UIO[CoderResult] = in.withJavaBuffer { jIn => - out.withJavaBuffer(jOut => IO.effectTotal(CoderResult.fromJava(javaEncoder.encode(jIn, jOut, endOfInput)))) + out.withJavaBuffer(jOut => ZIO.succeed(CoderResult.fromJava(javaEncoder.encode(jIn, jOut, endOfInput)))) } - def flush(out: ByteBuffer): UIO[CoderResult] = - out.withJavaBuffer(jOut => UIO.effectTotal(CoderResult.fromJava(javaEncoder.flush(jOut)))) + def flush(out: ByteBuffer)(implicit trace: Trace): UIO[CoderResult] = + out.withJavaBuffer(jOut => ZIO.succeed(CoderResult.fromJava(javaEncoder.flush(jOut)))) - def malformedInputAction: UIO[j.CodingErrorAction] = UIO.effectTotal(javaEncoder.malformedInputAction()) + def malformedInputAction(implicit trace: Trace): UIO[j.CodingErrorAction] = + ZIO.succeed(javaEncoder.malformedInputAction()) - def onMalformedInput(errorAction: j.CodingErrorAction): UIO[Unit] = - UIO.effectTotal(javaEncoder.onMalformedInput(errorAction)).unit + def onMalformedInput(errorAction: j.CodingErrorAction)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaEncoder.onMalformedInput(errorAction)).unit - def unmappableCharacterAction: UIO[j.CodingErrorAction] = UIO.effectTotal(javaEncoder.unmappableCharacterAction()) + def unmappableCharacterAction(implicit trace: Trace): UIO[j.CodingErrorAction] = + ZIO.succeed(javaEncoder.unmappableCharacterAction()) - def onUnmappableCharacter(errorAction: j.CodingErrorAction): UIO[Unit] = - UIO.effectTotal(javaEncoder.onUnmappableCharacter(errorAction)).unit + def onUnmappableCharacter(errorAction: j.CodingErrorAction)(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaEncoder.onUnmappableCharacter(errorAction)).unit def maxCharsPerByte: Float = javaEncoder.maxBytesPerChar() - def replacement: UIO[Chunk[Byte]] = UIO.effectTotal(Chunk.fromArray(javaEncoder.replacement())) + def replacement(implicit trace: Trace): UIO[Chunk[Byte]] = + ZIO.succeed(Chunk.fromArray(javaEncoder.replacement())) - def replaceWith(replacement: Chunk[Byte]): UIO[Unit] = - UIO.effectTotal(javaEncoder.replaceWith(replacement.toArray)).unit + def replaceWith(replacement: Chunk[Byte])(implicit trace: Trace): UIO[Unit] = + ZIO.succeed(javaEncoder.replaceWith(replacement.toArray)).unit /** * Resets this decoder, clearing any internal state. */ - def reset: UIO[Unit] = UIO.effectTotal(javaEncoder.reset()).unit + def reset(implicit trace: Trace): UIO[Unit] = ZIO.succeed(javaEncoder.reset()).unit /** * Encodes a stream of characters into bytes according to this character set's encoding. @@ -65,12 +68,14 @@ final class CharsetEncoder private (val javaEncoder: j.CharsetEncoder) extends A * @param bufSize * The size of the internal buffer used for encoding. Must be at least 50. */ - def transducer(bufSize: Int = 5000): Transducer[j.CharacterCodingException, Char, Byte] = { - val push: Managed[Nothing, Option[Chunk[Char]] => IO[j.CharacterCodingException, Chunk[Byte]]] = { + def transducer( + bufSize: Int = 5000 + )(implicit trace: Trace): ZPipeline[Any, j.CharacterCodingException, Char, Byte] = { + val push: ZIO[Any, Nothing, Option[Chunk[Char]] => IO[j.CharacterCodingException, Chunk[Byte]]] = { for { - _ <- reset.toManaged_ - charBuffer <- Buffer.char((bufSize.toFloat / this.averageBytesPerChar).round).toManaged_ - byteBuffer <- Buffer.byte(bufSize).toManaged_ + _ <- reset + charBuffer <- Buffer.char((bufSize.toFloat / this.averageBytesPerChar).round) + byteBuffer <- Buffer.byte(bufSize) } yield { def handleCoderResult(coderResult: CoderResult) = @@ -81,13 +86,15 @@ final class CharsetEncoder private (val javaEncoder: j.CharsetEncoder) extends A byteBuffer.getChunk() <* byteBuffer.clear case CoderResult.Malformed(length) => - IO.fail(new MalformedInputException(length)) + ZIO.fail(new MalformedInputException(length)) case CoderResult.Unmappable(length) => - IO.fail(new UnmappableCharacterException(length)) + ZIO.fail(new UnmappableCharacterException(length)) } (_: Option[Chunk[Char]]).map { inChunk => - def encodeChunk(inChars: Chunk[Char]): IO[j.CharacterCodingException, Chunk[Byte]] = + def encodeChunk(inChars: Chunk[Char])(implicit + trace: Trace + ): IO[j.CharacterCodingException, Chunk[Byte]] = for { bufRemaining <- charBuffer.remaining (decodeChars, remainingChars) = { @@ -100,23 +107,23 @@ final class CharsetEncoder private (val javaEncoder: j.CharsetEncoder) extends A _ <- charBuffer.flip result <- encode(charBuffer, byteBuffer, endOfInput = false) encodedBytes <- handleCoderResult(result) - remainderBytes <- if (remainingChars.isEmpty) IO.succeed(Chunk.empty) else encodeChunk(remainingChars) + remainderBytes <- if (remainingChars.isEmpty) ZIO.succeed(Chunk.empty) else encodeChunk(remainingChars) } yield encodedBytes ++ remainderBytes encodeChunk(inChunk) }.getOrElse { - def endOfInput: IO[j.CharacterCodingException, Chunk[Byte]] = + def endOfInput(implicit trace: Trace): IO[j.CharacterCodingException, Chunk[Byte]] = for { result <- encode(charBuffer, byteBuffer, endOfInput = true) encodedBytes <- handleCoderResult(result) - remainderBytes <- if (result == CoderResult.Overflow) endOfInput else IO.succeed(Chunk.empty) + remainderBytes <- if (result == CoderResult.Overflow) endOfInput else ZIO.succeed(Chunk.empty) } yield encodedBytes ++ remainderBytes charBuffer.flip *> endOfInput.flatMap { encodedBytes => - def flushRemaining: IO[j.CharacterCodingException, Chunk[Byte]] = + def flushRemaining(implicit trace: Trace): IO[j.CharacterCodingException, Chunk[Byte]] = for { result <- flush(byteBuffer) encodedBytes <- handleCoderResult(result) - remainderBytes <- if (result == CoderResult.Overflow) flushRemaining else IO.succeed(Chunk.empty) + remainderBytes <- if (result == CoderResult.Overflow) flushRemaining else ZIO.succeed(Chunk.empty) } yield encodedBytes ++ remainderBytes flushRemaining.map(encodedBytes ++ _) } <* charBuffer.clear <* byteBuffer.clear @@ -124,10 +131,12 @@ final class CharsetEncoder private (val javaEncoder: j.CharsetEncoder) extends A } } - if (bufSize < 50) - ZTransducer.die(new IllegalArgumentException(s"Buffer size is $bufSize, must be >= 50")) - else - ZTransducer(push) + if (bufSize < 50) { + ZPipeline.fromChannel( + ZChannel.fromZIO(ZIO.die(new IllegalArgumentException(s"Buffer size is $bufSize, must be >= 50"))) + ) + } else + ZPipeline.fromPush(push) } } diff --git a/nio/src/main/scala/zio/nio/charset/CoderResult.scala b/nio/shared/src/main/scala/zio/nio/charset/CoderResult.scala similarity index 100% rename from nio/src/main/scala/zio/nio/charset/CoderResult.scala rename to nio/shared/src/main/scala/zio/nio/charset/CoderResult.scala diff --git a/nio/src/main/scala/zio/nio/file/FileSystem.scala b/nio/shared/src/main/scala/zio/nio/file/FileSystem.scala similarity index 52% rename from nio/src/main/scala/zio/nio/file/FileSystem.scala rename to nio/shared/src/main/scala/zio/nio/file/FileSystem.scala index 05b2fa84..a412f2c4 100644 --- a/nio/src/main/scala/zio/nio/file/FileSystem.scala +++ b/nio/shared/src/main/scala/zio/nio/file/FileSystem.scala @@ -1,8 +1,9 @@ package zio.nio package file -import zio.blocking.{Blocking, effectBlockingIO} -import zio.{IO, UIO, ZIO, ZManaged} +import zio.ZIO.attemptBlockingIO + +import zio.{IO, Scope, Trace, UIO, ZIO} import java.io.IOException import java.net.URI @@ -10,22 +11,28 @@ import java.nio.file.attribute.UserPrincipalLookupService import java.nio.{file => jf} import scala.jdk.CollectionConverters._ -final class FileSystem private (private val javaFileSystem: jf.FileSystem) extends IOCloseable { +final class FileSystem private (private val javaFileSystem: jf.FileSystem) + extends IOCloseable + with FileSystemPlatformSpecific { + + override def jFileSystem: jf.FileSystem = javaFileSystem def provider: jf.spi.FileSystemProvider = javaFileSystem.provider() - def close: IO[IOException, Unit] = IO.effect(javaFileSystem.close()).refineToOrDie[IOException] + def close(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(javaFileSystem.close()).refineToOrDie[IOException] - def isOpen: UIO[Boolean] = UIO.effectTotal(javaFileSystem.isOpen()) + def isOpen(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(javaFileSystem.isOpen()) def isReadOnly: Boolean = javaFileSystem.isReadOnly def getSeparator: String = javaFileSystem.getSeparator - def getRootDirectories: UIO[List[Path]] = - UIO.effectTotal(javaFileSystem.getRootDirectories.asScala.map(Path.fromJava).toList) + def getRootDirectories(implicit trace: Trace): UIO[List[Path]] = + ZIO.succeed(javaFileSystem.getRootDirectories.asScala.map(Path.fromJava).toList) - def getFileStores: UIO[List[jf.FileStore]] = UIO.effectTotal(javaFileSystem.getFileStores.asScala.toList) + def getFileStores(implicit trace: Trace): UIO[List[jf.FileStore]] = + ZIO.succeed(javaFileSystem.getFileStores.asScala.toList) def supportedFileAttributeViews: Set[String] = javaFileSystem.supportedFileAttributeViews().asScala.toSet @@ -35,9 +42,6 @@ final class FileSystem private (private val javaFileSystem: jf.FileSystem) exten def getUserPrincipalLookupService: UserPrincipalLookupService = javaFileSystem.getUserPrincipalLookupService - def newWatchService: ZManaged[Blocking, IOException, WatchService] = - effectBlockingIO(WatchService.fromJava(javaFileSystem.newWatchService())).toNioManaged - } object FileSystem { @@ -55,16 +59,22 @@ object FileSystem { */ def default: FileSystem = new FileSystem(jf.FileSystems.getDefault) - def getFileSystem(uri: URI): ZIO[Blocking, Exception, FileSystem] = - effectBlockingIO(new FileSystem(jf.FileSystems.getFileSystem(uri))) + def getFileSystem(uri: URI)(implicit trace: Trace): ZIO[Any, Exception, FileSystem] = + attemptBlockingIO(new FileSystem(jf.FileSystems.getFileSystem(uri))) - def newFileSystem(uri: URI, env: (String, Any)*): ZManaged[Blocking, IOException, FileSystem] = - effectBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(uri, env.toMap.asJava))).toNioManaged + def newFileSystem(uri: URI, env: (String, Any)*)(implicit + trace: Trace + ): ZIO[Scope, IOException, FileSystem] = + attemptBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(uri, env.toMap.asJava))).toNioScoped - def newFileSystem(uri: URI, env: Map[String, _], loader: ClassLoader): ZManaged[Blocking, Exception, FileSystem] = - effectBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(uri, env.asJava, loader))).toNioManaged + def newFileSystem(uri: URI, env: Map[String, _], loader: ClassLoader)(implicit + trace: Trace + ): ZIO[Scope, Exception, FileSystem] = + attemptBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(uri, env.asJava, loader))).toNioScoped - def newFileSystem(path: Path, loader: ClassLoader): ZManaged[Blocking, IOException, FileSystem] = - effectBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(path.javaPath, loader))).toNioManaged + def newFileSystem(path: Path, loader: ClassLoader)(implicit + trace: Trace + ): ZIO[Scope, IOException, FileSystem] = + attemptBlockingIO(new FileSystem(jf.FileSystems.newFileSystem(path.javaPath, loader))).toNioScoped } diff --git a/nio/shared/src/main/scala/zio/nio/file/Files.scala b/nio/shared/src/main/scala/zio/nio/file/Files.scala new file mode 100644 index 00000000..60551c39 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/file/Files.scala @@ -0,0 +1,393 @@ +package zio.nio.file + +import zio.ZIO.attemptBlocking +import zio.nio.charset.Charset +import zio.nio.ZStreamHelper + +import zio.stream.ZStream +import zio.{Chunk, Scope, Trace, ZIO} + +import java.io.IOException +import java.nio.file.attribute._ +import java.nio.file.{ + CopyOption, + DirectoryStream, + FileStore, + FileVisitOption, + Files => JFiles, + LinkOption, + OpenOption, + Path => JPath +} +import java.util.function.BiPredicate +import scala.jdk.CollectionConverters._ +import scala.reflect._ + +object Files extends FilesPlatformSpecific { + + def newDirectoryStream(dir: Path, glob: String = "*")(implicit + trace: Trace + ): ZStream[Any, IOException, Path] = { + val scoped = ZIO + .fromAutoCloseable(attemptBlocking(JFiles.newDirectoryStream(dir.javaPath, glob))) + .map(_.iterator()) + ZStream.fromJavaIteratorScoped(scoped).map(Path.fromJava).refineToOrDie[IOException] + } + + def newDirectoryStream(dir: Path, filter: Path => Boolean)(implicit + trace: Trace + ): ZStream[Any, IOException, Path] = { + val javaFilter: DirectoryStream.Filter[_ >: JPath] = javaPath => filter(Path.fromJava(javaPath)) + val scoped = ZIO + .fromAutoCloseable(attemptBlocking(JFiles.newDirectoryStream(dir.javaPath, javaFilter))) + .map(_.iterator()) + ZStream.fromJavaIteratorScoped(scoped).map(Path.fromJava).refineToOrDie[IOException] + } + + def createFile(path: Path, attrs: FileAttribute[_]*)(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.createFile(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] + + def createDirectory(path: Path, attrs: FileAttribute[_]*)(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.createDirectory(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] + + def createDirectories(path: Path, attrs: FileAttribute[_]*)(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.createDirectories(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] + + def createTempFileIn( + dir: Path, + suffix: String = ".tmp", + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Any, IOException, Path] = + attemptBlocking(Path.fromJava(JFiles.createTempFile(dir.javaPath, prefix.orNull, suffix, fileAttributes.toSeq: _*))) + .refineToOrDie[IOException] + + def createTempFileInScoped( + dir: Path, + suffix: String = ".tmp", + prefix: Option[String] = None, + fileAttributes: Iterable[FileAttribute[_]] = Nil + )(implicit trace: Trace): ZIO[Scope, IOException, Path] = + ZIO.acquireRelease(createTempFileIn(dir, suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) + + def createTempFile( + suffix: String = ".tmp", + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Any, IOException, Path] = + attemptBlocking(Path.fromJava(JFiles.createTempFile(prefix.orNull, suffix, fileAttributes.toSeq: _*))) + .refineToOrDie[IOException] + + def createTempFileScoped( + suffix: String = ".tmp", + prefix: Option[String] = None, + fileAttributes: Iterable[FileAttribute[_]] = Nil + )(implicit trace: Trace): ZIO[Scope, IOException, Path] = + ZIO.acquireRelease(createTempFile(suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) + + def createTempDirectory( + dir: Path, + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Any, IOException, Path] = + attemptBlocking(Path.fromJava(JFiles.createTempDirectory(dir.javaPath, prefix.orNull, fileAttributes.toSeq: _*))) + .refineToOrDie[IOException] + + def createTempDirectoryScoped( + dir: Path, + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Scope, IOException, Path] = + ZIO.acquireRelease(createTempDirectory(dir, prefix, fileAttributes))(release = deleteRecursive(_).ignore) + + def createTempDirectory( + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Any, IOException, Path] = + attemptBlocking(Path.fromJava(JFiles.createTempDirectory(prefix.orNull, fileAttributes.toSeq: _*))) + .refineToOrDie[IOException] + + def createTempDirectoryScoped( + prefix: Option[String], + fileAttributes: Iterable[FileAttribute[_]] + )(implicit trace: Trace): ZIO[Scope, IOException, Path] = + ZIO.acquireRelease(createTempDirectory(prefix, fileAttributes))(release = deleteRecursive(_).ignore) + + def createSymbolicLink( + link: Path, + target: Path, + fileAttributes: FileAttribute[_]* + )(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.createSymbolicLink(link.javaPath, target.javaPath, fileAttributes: _*)).unit + .refineToOrDie[IOException] + + def createLink(link: Path, existing: Path)(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.createLink(link.javaPath, existing.javaPath)).unit.refineToOrDie[IOException] + + def delete(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.delete(path.javaPath)).refineToOrDie[IOException] + + def deleteIfExists(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Boolean] = + attemptBlocking(JFiles.deleteIfExists(path.javaPath)).refineToOrDie[IOException] + + def copy(source: Path, target: Path, copyOptions: CopyOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.copy(source.javaPath, target.javaPath, copyOptions: _*)).unit + .refineToOrDie[IOException] + + def move(source: Path, target: Path, copyOptions: CopyOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.move(source.javaPath, target.javaPath, copyOptions: _*)).unit.refineToOrDie[IOException] + + def readSymbolicLink(link: Path)(implicit trace: Trace): ZIO[Any, IOException, Path] = + attemptBlocking(Path.fromJava(JFiles.readSymbolicLink(link.javaPath))).refineToOrDie[IOException] + + def getFileStore(path: Path)(implicit trace: Trace): ZIO[Any, IOException, FileStore] = + attemptBlocking(JFiles.getFileStore(path.javaPath)).refineToOrDie[IOException] + + def isSameFile(path: Path, path2: Path)(implicit trace: Trace): ZIO[Any, IOException, Boolean] = + attemptBlocking(JFiles.isSameFile(path.javaPath, path2.javaPath)).refineToOrDie[IOException] + + def isHidden(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Boolean] = + attemptBlocking(JFiles.isHidden(path.javaPath)).refineToOrDie[IOException] + + def probeContentType(path: Path)(implicit trace: Trace): ZIO[Any, IOException, String] = + attemptBlocking(JFiles.probeContentType(path.javaPath)).refineToOrDie[IOException] + + def useFileAttributeView[A <: FileAttributeView: ClassTag, B, E](path: Path, linkOptions: LinkOption*)( + f: A => ZIO[Any, E, B] + )(implicit trace: Trace): ZIO[Any, E, B] = { + val viewClass = + classTag[A].runtimeClass.asInstanceOf[Class[A]] // safe? because we know A is a subtype of FileAttributeView + attemptBlocking(JFiles.getFileAttributeView[A](path.javaPath, viewClass, linkOptions: _*)).orDie + .flatMap(f) + } + + def readAttributes[A <: BasicFileAttributes: ClassTag]( + path: Path, + linkOptions: LinkOption* + )(implicit trace: Trace): ZIO[Any, IOException, A] = { + // safe? because we know A is a subtype of BasicFileAttributes + val attributeClass = classTag[A].runtimeClass.asInstanceOf[Class[A]] + attemptBlocking(JFiles.readAttributes(path.javaPath, attributeClass, linkOptions: _*)) + .refineToOrDie[IOException] + } + + final case class Attribute(attributeName: String, viewName: String = "basic") { + def toJava: String = s"$viewName:$attributeName" + } + + object Attribute { + + def fromJava(javaAttribute: String): Option[Attribute] = + javaAttribute.split(':').toList match { + case name :: Nil => Some(Attribute(name)) + case view :: name :: Nil => Some(Attribute(name, view)) + case _ => None + } + + } + + def setAttribute( + path: Path, + attribute: Attribute, + value: Object, + linkOptions: LinkOption* + )(implicit trace: Trace): ZIO[Any, Exception, Unit] = + attemptBlocking(JFiles.setAttribute(path.javaPath, attribute.toJava, value, linkOptions: _*)).unit + .refineToOrDie[Exception] + + def getAttribute(path: Path, attribute: Attribute, linkOptions: LinkOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, Object] = + attemptBlocking(JFiles.getAttribute(path.javaPath, attribute.toJava, linkOptions: _*)).refineToOrDie[IOException] + + sealed trait AttributeNames { + + def toJava: String = + this match { + case AttributeNames.All => "*" + case AttributeNames.List(names) => names.mkString(",") + } + + } + + object AttributeNames { + final case class List(names: scala.List[String]) extends AttributeNames + + case object All extends AttributeNames + + def fromJava(javaNames: String): AttributeNames = + javaNames.trim match { + case "*" => All + case list => List(list.split(',').toList) + } + + } + + final case class Attributes(attributeNames: AttributeNames, viewName: String = "base") { + def toJava: String = s"$viewName:${attributeNames.toJava}" + } + + object Attributes { + + def fromJava(javaAttributes: String): Option[Attributes] = + javaAttributes.split(':').toList match { + case names :: Nil => Some(Attributes(AttributeNames.fromJava(names))) + case view :: names :: Nil => Some(Attributes(AttributeNames.fromJava(names), view)) + case _ => None + } + + } + + def readAttributes( + path: Path, + attributes: Attributes, + linkOptions: LinkOption* + )(implicit trace: Trace): ZIO[Any, IOException, Map[String, AnyRef]] = + attemptBlocking(JFiles.readAttributes(path.javaPath, attributes.toJava, linkOptions: _*)) + .map(_.asScala.toMap) + .refineToOrDie[IOException] + + def getPosixFilePermissions( + path: Path, + linkOptions: LinkOption* + )(implicit trace: Trace): ZIO[Any, IOException, Set[PosixFilePermission]] = + attemptBlocking(JFiles.getPosixFilePermissions(path.javaPath, linkOptions: _*)) + .map(_.asScala.toSet) + .refineToOrDie[IOException] + + def setPosixFilePermissions(path: Path, permissions: Set[PosixFilePermission])(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.setPosixFilePermissions(path.javaPath, permissions.asJava)).unit + .refineToOrDie[IOException] + + def getOwner(path: Path, linkOptions: LinkOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, UserPrincipal] = + attemptBlocking(JFiles.getOwner(path.javaPath, linkOptions: _*)).refineToOrDie[IOException] + + def setOwner(path: Path, owner: UserPrincipal)(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.setOwner(path.javaPath, owner)).unit.refineToOrDie[IOException] + + def isSymbolicLink(path: Path)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.isSymbolicLink(path.javaPath)).orDie + + def isDirectory(path: Path, linkOptions: LinkOption*)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.isDirectory(path.javaPath, linkOptions: _*)).orDie + + def isRegularFile(path: Path, linkOptions: LinkOption*)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.isRegularFile(path.javaPath, linkOptions: _*)).orDie + + def getLastModifiedTime(path: Path, linkOptions: LinkOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, FileTime] = + attemptBlocking(JFiles.getLastModifiedTime(path.javaPath, linkOptions: _*)).refineToOrDie[IOException] + + def setLastModifiedTime(path: Path, time: FileTime)(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.setLastModifiedTime(path.javaPath, time)).unit.refineToOrDie[IOException] + + def size(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Long] = + attemptBlocking(JFiles.size(path.javaPath)).refineToOrDie[IOException] + + def exists(path: Path, linkOptions: LinkOption*)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.exists(path.javaPath, linkOptions: _*)).orDie + + def notExists(path: Path, linkOptions: LinkOption*)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.notExists(path.javaPath, linkOptions: _*)).orDie + + def isReadable(path: Path)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = attemptBlocking( + JFiles.isReadable(path.javaPath) + ).orDie + + def isWritable(path: Path)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = attemptBlocking( + JFiles.isWritable(path.javaPath) + ).orDie + + def isExecutable(path: Path)(implicit trace: Trace): ZIO[Any, Nothing, Boolean] = + attemptBlocking(JFiles.isExecutable(path.javaPath)).orDie + + def readAllBytes(path: Path)(implicit trace: Trace): ZIO[Any, IOException, Chunk[Byte]] = + attemptBlocking(Chunk.fromArray(JFiles.readAllBytes(path.javaPath))).refineToOrDie[IOException] + + def readAllLines(path: Path, charset: Charset = Charset.Standard.utf8)(implicit + trace: Trace + ): ZIO[Any, IOException, List[String]] = + attemptBlocking(JFiles.readAllLines(path.javaPath, charset.javaCharset).asScala.toList).refineToOrDie[IOException] + + def writeBytes(path: Path, bytes: Chunk[Byte], openOptions: OpenOption*)(implicit + trace: Trace + ): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.write(path.javaPath, bytes.toArray, openOptions: _*)).unit.refineToOrDie[IOException] + + def writeLines( + path: Path, + lines: Iterable[CharSequence], + charset: Charset = Charset.Standard.utf8, + openOptions: Set[OpenOption] = Set.empty + )(implicit trace: Trace): ZIO[Any, IOException, Unit] = + attemptBlocking(JFiles.write(path.javaPath, lines.asJava, charset.javaCharset, openOptions.toSeq: _*)).unit + .refineToOrDie[IOException] + + def lines(path: Path, charset: Charset = Charset.Standard.utf8)(implicit + trace: Trace + ): ZStream[Any, IOException, String] = + ZStreamHelper + .fromJavaStreamScoped( + ZIO.fromAutoCloseable(attemptBlocking(JFiles.lines(path.javaPath, charset.javaCharset))) + ) + .refineToOrDie[IOException] + + def list(path: Path)(implicit trace: Trace): ZStream[Any, IOException, Path] = + ZStreamHelper + .fromJavaStreamScoped( + ZIO.fromAutoCloseable(attemptBlocking(JFiles.list(path.javaPath))) + ) + .map(Path.fromJava) + .refineToOrDie[IOException] + + def walk( + path: Path, + maxDepth: Int = Int.MaxValue, + visitOptions: Set[FileVisitOption] = Set.empty + )(implicit trace: Trace): ZStream[Any, IOException, Path] = + ZStreamHelper + .fromJavaStreamScoped( + ZIO.fromAutoCloseable(attemptBlocking(JFiles.walk(path.javaPath, maxDepth, visitOptions.toSeq: _*))) + ) + .map(Path.fromJava) + .refineToOrDie[IOException] + + def find(path: Path, maxDepth: Int = Int.MaxValue, visitOptions: Set[FileVisitOption] = Set.empty)( + test: (Path, BasicFileAttributes) => Boolean + )(implicit trace: Trace): ZStream[Any, IOException, Path] = { + val matcher: BiPredicate[JPath, BasicFileAttributes] = (path, attr) => test(Path.fromJava(path), attr) + ZStreamHelper + .fromJavaStreamScoped( + ZIO.fromAutoCloseable( + attemptBlocking(JFiles.find(path.javaPath, maxDepth, matcher, visitOptions.toSeq: _*)) + ) + ) + .map(Path.fromJava) + .refineToOrDie[IOException] + } + + def copy( + in: ZStream[Any, IOException, Byte], + target: Path, + options: CopyOption* + )(implicit trace: Trace): ZIO[Any, IOException, Long] = + ZIO.scoped { + in.toInputStream + .flatMap(inputStream => attemptBlocking(JFiles.copy(inputStream, target.javaPath, options: _*))) + .refineToOrDie[IOException] + } + +} diff --git a/nio/src/main/scala/zio/nio/file/Path.scala b/nio/shared/src/main/scala/zio/nio/file/Path.scala similarity index 84% rename from nio/src/main/scala/zio/nio/file/Path.scala rename to nio/shared/src/main/scala/zio/nio/file/Path.scala index a962121d..b49312b0 100644 --- a/nio/src/main/scala/zio/nio/file/Path.scala +++ b/nio/shared/src/main/scala/zio/nio/file/Path.scala @@ -1,7 +1,6 @@ package zio.nio.file -import zio.blocking.Blocking -import zio.{Chunk, ZIO} +import zio.{Chunk, Trace, ZIO} import java.io.{File, IOError, IOException} import java.net.URI @@ -47,17 +46,17 @@ final class Path private (private[nio] val javaPath: JPath) extends Watchable { def relativize(other: Path): Path = fromJava(javaPath.relativize(other.javaPath)) - def toUri: ZIO[Blocking, IOError, URI] = - ZIO.accessM[Blocking](_.get.effectBlocking(javaPath.toUri)).refineToOrDie[IOError] + def toUri(implicit trace: Trace): ZIO[Any, IOError, URI] = + ZIO.attemptBlocking(javaPath.toUri).refineToOrDie[IOError] - def toAbsolutePath: ZIO[Blocking, IOError, Path] = + def toAbsolutePath(implicit trace: Trace): ZIO[Any, IOError, Path] = ZIO - .accessM[Blocking](_.get.effectBlocking(fromJava(javaPath.toAbsolutePath))) + .attemptBlocking(fromJava(javaPath.toAbsolutePath)) .refineToOrDie[IOError] - def toRealPath(linkOptions: LinkOption*): ZIO[Blocking, IOException, Path] = + def toRealPath(linkOptions: LinkOption*)(implicit trace: Trace): ZIO[Any, IOException, Path] = ZIO - .accessM[Blocking](_.get.effectBlocking(fromJava(javaPath.toRealPath(linkOptions: _*)))) + .attemptBlocking(fromJava(javaPath.toRealPath(linkOptions: _*))) .refineToOrDie[IOException] def toFile: File = javaPath.toFile @@ -88,10 +87,10 @@ final class Path private (private[nio] val javaPath: JPath) extends Watchable { events: Iterable[WatchEvent.Kind[_]], maxDepth: Int = Int.MaxValue, modifiers: Iterable[WatchEvent.Modifier] = Iterable.empty - ): ZIO[Blocking, IOException, Chunk[WatchKey]] = + )(implicit trace: Trace): ZIO[Any, IOException, Chunk[WatchKey]] = Files .find(path = this, maxDepth = maxDepth)((_, a) => a.isDirectory) - .mapM(dir => dir.register(watcher, events, modifiers.toSeq: _*)) + .mapZIO(dir => dir.register(watcher, events, modifiers.toSeq: _*)) .runCollect override protected def javaWatchable: JWatchable = javaPath diff --git a/nio/src/main/scala/zio/nio/file/WatchService.scala b/nio/shared/src/main/scala/zio/nio/file/WatchService.scala similarity index 72% rename from nio/src/main/scala/zio/nio/file/WatchService.scala rename to nio/shared/src/main/scala/zio/nio/file/WatchService.scala index 0b94fc08..1af3d690 100644 --- a/nio/src/main/scala/zio/nio/file/WatchService.scala +++ b/nio/shared/src/main/scala/zio/nio/file/WatchService.scala @@ -1,9 +1,8 @@ package zio.nio.file import zio._ -import zio.blocking.Blocking -import zio.duration.Duration import zio.nio.IOCloseable + import zio.stream.ZStream import java.io.IOException @@ -20,15 +19,18 @@ import scala.jdk.CollectionConverters._ trait Watchable { protected def javaWatchable: JWatchable - final def register(watcher: WatchService, events: WatchEvent.Kind[_]*): IO[IOException, WatchKey] = - IO.effect(new WatchKey(javaWatchable.register(watcher.javaWatchService, events: _*))).refineToOrDie[IOException] + final def register(watcher: WatchService, events: WatchEvent.Kind[_]*)(implicit + trace: Trace + ): IO[IOException, WatchKey] = + ZIO.attempt(new WatchKey(javaWatchable.register(watcher.javaWatchService, events: _*))).refineToOrDie[IOException] final def register( watcher: WatchService, events: Iterable[WatchEvent.Kind[_]], modifiers: WatchEvent.Modifier* - ): IO[IOException, WatchKey] = - IO.effect(new WatchKey(javaWatchable.register(watcher.javaWatchService, events.toArray, modifiers: _*))) + )(implicit trace: Trace): IO[IOException, WatchKey] = + ZIO + .attempt(new WatchKey(javaWatchable.register(watcher.javaWatchService, events.toArray, modifiers: _*))) .refineToOrDie[IOException] } @@ -49,31 +51,33 @@ object Watchable { */ final class WatchKey private[file] (private val javaKey: JWatchKey) { - def isValid: UIO[Boolean] = UIO.effectTotal(javaKey.isValid) + def isValid(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(javaKey.isValid) /** * Retrieves and removes all pending events for this watch key. * * This does not block, it will immediately return an empty list if there are no events pending. Typically, this key - * should be reset after processing the returned events, the `pollEventsManaged` method can be used to do this + * should be reset after processing the returned events, the `pollEventsScoped` method can be used to do this * automatically and reliably. */ - def pollEvents: UIO[List[WatchEvent[_]]] = UIO.effectTotal(javaKey.pollEvents().asScala.toList) + def pollEvents(implicit trace: Trace): UIO[List[WatchEvent[_]]] = + ZIO.succeed(javaKey.pollEvents().asScala.toList) /** - * Retrieves and removes all pending events for this watch key as a managed resource. + * Retrieves and removes all pending events for this watch key as a scoped resource. * - * This does not block, it will immediately return an empty list if there are no events pending. When the returned - * `Managed` completed, this key will be '''reset'''. + * This does not block, it will immediately return an empty list if there are no events pending. When the `Scope` is + * closed, this key will be '''reset'''. */ - def pollEventsManaged: Managed[Nothing, List[WatchEvent[_]]] = pollEvents.toManaged_.ensuring(reset) + def pollEventsScoped(implicit trace: Trace): ZIO[Scope, Nothing, List[WatchEvent[_]]] = + pollEvents.withFinalizer(_ => reset) /** * Resets this watch key, making it eligible to be re-queued in the `WatchService`. A key is typically reset after all - * the pending events retrieved from `pollEvents` have been processed. Use `pollEventsManaged` to automatically and + * the pending events retrieved from `pollEvents` have been processed. Use `pollEventsScop[ed` to automatically and * reliably perform a reset. */ - def reset: UIO[Boolean] = UIO.effectTotal(javaKey.reset()) + def reset(implicit trace: Trace): UIO[Boolean] = ZIO.succeed(javaKey.reset()) /** * Cancels the registration with the watch service. Upon return the watch key will be invalid. If the watch key is @@ -82,7 +86,7 @@ final class WatchKey private[file] (private val javaKey: JWatchKey) { * cancelled. If this watch key has already been cancelled then invoking this method has no effect. Once cancelled, a * watch key remains forever invalid. */ - def cancel: UIO[Unit] = UIO.effectTotal(javaKey.cancel()) + def cancel(implicit trace: Trace): UIO[Unit] = ZIO.succeed(javaKey.cancel()) /** * Returns the object for which this watch key was created. @@ -127,13 +131,15 @@ final class WatchKey private[file] (private val javaKey: JWatchKey) { */ final class WatchService private (private[file] val javaWatchService: JWatchService) extends IOCloseable { - def close: IO[IOException, Unit] = IO.effect(javaWatchService.close()).refineToOrDie[IOException] + def close(implicit trace: Trace): IO[IOException, Unit] = + ZIO.attempt(javaWatchService.close()).refineToOrDie[IOException] - def poll: UIO[Option[WatchKey]] = IO.effectTotal(Option(javaWatchService.poll()).map(new WatchKey(_))) + def poll(implicit trace: Trace): UIO[Option[WatchKey]] = + ZIO.succeed(Option(javaWatchService.poll()).map(new WatchKey(_))) - def poll(timeout: Duration): URIO[Blocking, Option[WatchKey]] = - blocking - .effectBlockingInterrupt( + def poll(timeout: Duration)(implicit trace: Trace): URIO[Any, Option[WatchKey]] = + ZIO + .attemptBlockingInterrupt( Option(javaWatchService.poll(timeout.toNanos, TimeUnit.NANOSECONDS)).map(new WatchKey(_)) ) .orDie @@ -141,7 +147,8 @@ final class WatchService private (private[file] val javaWatchService: JWatchServ /** * Retrieves and removes next watch key, waiting if none are yet present. */ - def take: URIO[Blocking, WatchKey] = blocking.effectBlockingInterrupt(new WatchKey(javaWatchService.take())).orDie + def take(implicit trace: Trace): URIO[Any, WatchKey] = + ZIO.attemptBlockingInterrupt(new WatchKey(javaWatchService.take())).orDie /** * A stream of signalled objects which have pending events. @@ -149,13 +156,11 @@ final class WatchService private (private[file] val javaWatchService: JWatchServ * Note the `WatchKey` objects returned by this stream must be reset before they will be queued again with any * additional events. */ - def stream: ZStream[Blocking, Nothing, WatchKey] = ZStream.repeatEffect(take) + def stream(implicit trace: Trace): ZStream[Any, Nothing, WatchKey] = ZStream.repeatZIO(take) } -object WatchService { - - def forDefaultFileSystem: ZManaged[Blocking, IOException, WatchService] = FileSystem.default.newWatchService +object WatchService extends WatchServicePlatformSpecific { def fromJava(javaWatchService: JWatchService): WatchService = new WatchService(javaWatchService) diff --git a/nio/src/main/scala/zio/nio/file/package.scala b/nio/shared/src/main/scala/zio/nio/file/package.scala similarity index 100% rename from nio/src/main/scala/zio/nio/file/package.scala rename to nio/shared/src/main/scala/zio/nio/file/package.scala diff --git a/nio/shared/src/main/scala/zio/nio/package.scala b/nio/shared/src/main/scala/zio/nio/package.scala new file mode 100644 index 00000000..e3c1c104 --- /dev/null +++ b/nio/shared/src/main/scala/zio/nio/package.scala @@ -0,0 +1,50 @@ +package zio + +import java.io.EOFException + +/** + * ZIO-NIO, the API for using Java's NIO API in ZIO programs. + */ +package object nio { + + /** + * Handle -1 magic number returned by many Java read APIs when end of file is reached. + * + * Produces an `EOFException` failure if `value` < 0, otherwise succeeds with `value`. + */ + private[nio] def eofCheck(value: Int)(implicit trace: Trace): IO[EOFException, Int] = + if (value < 0) ZIO.fail(new EOFException("Channel has reached the end of stream")) else ZIO.succeed(value) + + /** + * Handle -1 magic number returned by many Java read APIs when end of file is reached. + * + * Produces an `EOFException` failure if `value` < 0, otherwise succeeds with `value`. + */ + private[nio] def eofCheck(value: Long)(implicit trace: Trace): IO[EOFException, Long] = + if (value < 0L) ZIO.fail(new EOFException("Channel has reached the end of stream")) else ZIO.succeed(value) + + implicit final class EffectOps[-R, +E, +A](private val effect: ZIO[R, E, A]) extends AnyVal { + + /** + * Explicitly represent end-of-stream in the error channel. + * + * This will catch an `EOFException` failure from the effect and translate it to a failure of `None`. Other + * exception types are wrapped in `Some`. + */ + def eofCheck[E2 >: E](implicit ev: EOFException <:< E2, trace: Trace): ZIO[R, Option[E2], A] = + effect.catchAll { + case _: EOFException => ZIO.fail(None) + case e => ZIO.fail(Some(e)) + } + + } + + implicit final private[nio] class IOCloseableManagement[-R, +E, +A <: IOCloseable]( + private val acquire: ZIO[R, E, A] + ) extends AnyVal { + + def toNioScoped(implicit trace: Trace): ZIO[R with Scope, E, A] = + acquire.tap(a => ZIO.addFinalizer(a.close.ignore)) + + } +} diff --git a/nio/src/test/resources/async_file_read_test.txt b/nio/shared/src/test/resources/async_file_read_test.txt similarity index 100% rename from nio/src/test/resources/async_file_read_test.txt rename to nio/shared/src/test/resources/async_file_read_test.txt diff --git a/nio/src/test/resources/scattering_read_test.txt b/nio/shared/src/test/resources/scattering_read_test.txt similarity index 100% rename from nio/src/test/resources/scattering_read_test.txt rename to nio/shared/src/test/resources/scattering_read_test.txt diff --git a/nio/shared/src/test/scala/zio/nio/BaseSpec.scala b/nio/shared/src/test/scala/zio/nio/BaseSpec.scala new file mode 100644 index 00000000..7479a024 --- /dev/null +++ b/nio/shared/src/test/scala/zio/nio/BaseSpec.scala @@ -0,0 +1,8 @@ +package zio.nio + +import zio._ +import zio.test.{Live, TestAspect, TestAspectAtLeastR, ZIOSpecDefault} + +trait BaseSpec extends ZIOSpecDefault { + override def aspects: Chunk[TestAspectAtLeastR[Live]] = Chunk(TestAspect.timeout(60.seconds)) +} diff --git a/nio/src/test/scala/zio/nio/BufferSpec.scala b/nio/shared/src/test/scala/zio/nio/BufferSpec.scala similarity index 81% rename from nio/src/test/scala/zio/nio/BufferSpec.scala rename to nio/shared/src/test/scala/zio/nio/BufferSpec.scala index cf0eeafe..b3cde6fd 100644 --- a/nio/src/test/scala/zio/nio/BufferSpec.scala +++ b/nio/shared/src/test/scala/zio/nio/BufferSpec.scala @@ -1,9 +1,8 @@ package zio.nio import zio.test.Assertion._ -import zio.test._ -import zio.test.environment.TestEnvironment -import zio.{Chunk, IO} +import zio.test.{TestEnvironment, _} +import zio.{Chunk, IO, ZIO} import java.nio.{ Buffer => JBuffer, @@ -21,7 +20,7 @@ import scala.reflect.ClassTag object BufferSpec extends BaseSpec { - def spec: Spec[TestEnvironment, TestFailure[Exception], TestSuccess] = + def spec: Spec[TestEnvironment, Exception] = suite("BufferSpec")( commonBufferTests( "ByteBuffer", @@ -80,57 +79,57 @@ object BufferSpec extends BaseSpec { wrap: Chunk[A] => IO[Exception, C], jAllocate: Int => B, f: Int => A - ): ZSpec[TestEnvironment, Exception] = { + ): Spec[TestEnvironment, Exception] = { val initialCapacity = 10 def initialValue = f(1) def initialValues = Array(1, 2, 3).map(f) def zeroValues = Array(0, 0, 0).map(f) suite(suiteName)( - testM("apply") { + test("apply") { for { allocated <- allocate(3) array <- allocated.array } yield assert(array.toList)(hasSameElements(zeroValues.toList)) }, - testM("wrap backed by an array") { + test("wrap backed by an array") { for { buffer <- wrap(Chunk.fromArray(initialValues)) array <- buffer.array } yield assert(array.toList)(hasSameElements(initialValues.toList)) }, - testM("capacity") { + test("capacity") { for { allocated <- allocate(initialCapacity) capacity = allocated.capacity } yield assert(capacity)(equalTo(jAllocate(initialCapacity).capacity)) }, - testM("capacity initialized") { + test("capacity initialized") { for { allocated <- allocate(initialCapacity) capacity = allocated.capacity } yield assert(capacity)(equalTo(initialCapacity)) }, - testM("position is 0") { + test("position is 0") { for { allocated <- allocate(initialCapacity) position <- allocated.position } yield assert(position)(equalTo(0)) }, - testM("limit is capacity") { + test("limit is capacity") { for { allocated <- allocate(initialCapacity) limit <- allocated.limit } yield assert(limit)(equalTo(initialCapacity)) }, - testM("position set") { + test("position set") { for { buffer <- Buffer.byte(initialCapacity) _ <- buffer.position(3) position <- buffer.position } yield assert(position)(equalTo(3)) }, - testM("limit set") { + test("limit set") { for { buffer <- Buffer.byte(initialCapacity) limit = 3 @@ -138,7 +137,7 @@ object BufferSpec extends BaseSpec { newLimit <- buffer.limit } yield assert(newLimit)(equalTo(limit)) }, - testM("position reset") { + test("position reset") { for { buffer <- Buffer.byte(initialCapacity) newLimit = 3 @@ -147,7 +146,7 @@ object BufferSpec extends BaseSpec { position <- buffer.position } yield assert(position)(equalTo(newLimit)) }, - testM("reset to marked position") { + test("reset to marked position") { for { b <- allocate(initialCapacity) _ <- b.position(1) @@ -157,7 +156,7 @@ object BufferSpec extends BaseSpec { newPosition <- b.position } yield assert(newPosition)(equalTo(1)) }, - testM("clear") { + test("clear") { for { b <- allocate(initialCapacity) _ <- b.position(1) @@ -167,7 +166,7 @@ object BufferSpec extends BaseSpec { limit <- b.limit } yield assert(position)(equalTo(0)) && assert(limit)(equalTo(initialCapacity)) }, - testM("flip") { + test("flip") { for { b <- allocate(initialCapacity) _ <- b.position(1) @@ -176,7 +175,7 @@ object BufferSpec extends BaseSpec { limit <- b.limit } yield assert(position)(equalTo(0)) && assert(limit)(equalTo(1)) }, - testM("rewind sets position to 0") { + test("rewind sets position to 0") { for { b <- allocate(initialCapacity) _ <- b.position(1) @@ -184,12 +183,12 @@ object BufferSpec extends BaseSpec { newPosition <- b.position } yield assert(newPosition)(equalTo(0)) }, - testM("heap buffers a backed by an array") { + test("heap buffers a backed by an array") { for { b <- allocate(initialCapacity) } yield assert(b.hasArray)(isTrue) }, - testM("put writes an element and increments the position") { + test("put writes an element and increments the position") { for { b <- allocate(initialCapacity) _ <- b.put(initialValue) @@ -197,20 +196,20 @@ object BufferSpec extends BaseSpec { position <- b.position } yield assert(newValue)(equalTo(initialValue)) && assert(position)(equalTo(1)) }, - testM("failing put if there are no elements remaining") { + test("failing put if there are no elements remaining") { for { b <- allocate(0) - result <- b.put(initialValue).run + result <- b.put(initialValue).exit } yield assert(result)(dies(isSubtype[BufferOverflowException](anything))) }, - testM("failing put if this is a read-only buffer") { + test("failing put if this is a read-only buffer") { for { b <- allocate(initialCapacity) bReadOnly <- b.asReadOnlyBuffer - result <- bReadOnly.put(initialValue).run + result <- bReadOnly.put(initialValue).exit } yield assert(result)(dies(isSubtype[ReadOnlyBufferException](anything))) }, - testM("put writes an element at a specified index") { + test("put writes an element at a specified index") { for { b <- allocate(initialCapacity) _ <- b.put(1, initialValue) @@ -218,20 +217,20 @@ object BufferSpec extends BaseSpec { position <- b.position } yield assert(newValue)(equalTo(initialValue)) && assert(position)(equalTo(0)) }, - testM("failing put if the index is negative") { + test("failing put if the index is negative") { for { b <- allocate(initialCapacity) - result <- b.put(-1, initialValue).run + result <- b.put(-1, initialValue).exit } yield assert(result)(dies(isSubtype[IndexOutOfBoundsException](anything))) }, - testM("failing put if the index is not smaller than the limit") { + test("failing put if the index is not smaller than the limit") { for { b <- allocate(initialCapacity) - result <- b.put(initialCapacity, initialValue).run + result <- b.put(initialCapacity, initialValue).exit } yield assert(result)(dies(isSubtype[IndexOutOfBoundsException](anything))) }, - testM[TestEnvironment, Exception]("0 <= mark <= position <= limit <= capacity") { - checkM(Gen.int(-1, 10), Gen.int(-1, 10), Gen.int(-1, 10), Gen.int(-1, 10)) { + test("0 <= mark <= position <= limit <= capacity") { + check(Gen.int(-1, 10), Gen.int(-1, 10), Gen.int(-1, 10), Gen.int(-1, 10)) { (markedPosition: Int, position: Int, limit: Int, capacity: Int) => (for { b <- Buffer.byte(capacity) @@ -244,7 +243,7 @@ object BufferSpec extends BaseSpec { } yield assert(mark)(isWithin(0, position)) && assert(limit)( isWithin(position, capacity) )).catchSomeDefect { case _: IllegalArgumentException | _: IllegalStateException => - IO.effectTotal(assertCompletes) + ZIO.succeed(assertCompletes) } } } diff --git a/nio/shared/src/test/scala/zio/nio/channels/ChannelSpec.scala b/nio/shared/src/test/scala/zio/nio/channels/ChannelSpec.scala new file mode 100644 index 00000000..bb69b7c0 --- /dev/null +++ b/nio/shared/src/test/scala/zio/nio/channels/ChannelSpec.scala @@ -0,0 +1,27 @@ +package zio.nio.channels + +import zio._ +import zio.nio.{BaseSpec, EffectOps} +import zio.test.Assertion._ +import zio.test._ + +import java.io.{EOFException, FileNotFoundException, IOException} + +object ChannelSpec extends BaseSpec { + + override def spec = + suite("Channel")( + suite("explicit end-of-stream")( + test("converts EOFException to None") { + assertZIO(ZIO.fail(new EOFException).eofCheck.exit)(fails(isNone)) + }, + test("converts non EOFException to Some") { + val e: IOException = new FileNotFoundException() + assertZIO(ZIO.fail(e).eofCheck.exit)(fails(isSome(equalTo(e)))) + }, + test("passes through success") { + assertZIO(ZIO.succeed(42).eofCheck.exit)(succeeds(equalTo(42))) + } + ) + ) +} diff --git a/nio/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala b/nio/shared/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala similarity index 54% rename from nio/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala rename to nio/shared/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala index 465ff775..50ecad6f 100644 --- a/nio/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala +++ b/nio/shared/src/test/scala/zio/nio/channels/ScatterGatherChannelSpec.scala @@ -1,34 +1,25 @@ package zio.nio.channels -import zio.blocking.Blocking -import zio.clock.Clock import zio.nio.file.Path import zio.nio.{BaseSpec, Buffer} -import zio.random.Random import zio.test.Assertion._ import zio.test._ -import zio.test.environment.{Live, TestClock, TestConsole, TestRandom, TestSystem} -import zio.{Chunk, Has, IO, ZIO} +import zio.{Chunk, Scope, ZIO} +import java.io.IOException import java.nio.file.{Files, StandardOpenOption} import scala.io.Source object ScatterGatherChannelSpec extends BaseSpec { - override def spec: Spec[Any with Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = + override def spec: Spec[Scope with Any, IOException] = suite("ScatterGatherChannelSpec")( - testM("scattering read") { + test("scattering read") { FileChannel - .open(Path("nio/src/test/resources/scattering_read_test.txt"), StandardOpenOption.READ) - .useNioBlockingOps { ops => + .open(Path("nio/shared/src/test/resources/scattering_read_test.txt"), StandardOpenOption.READ) + .flatMapNioBlockingOps { ops => for { - buffs <- IO.collectAll(List(Buffer.byte(5), Buffer.byte(5))) + buffs <- ZIO.collectAll(List(Buffer.byte(5), Buffer.byte(5))) _ <- ops.read(buffs) list <- ZIO.foreach(buffs) { (buffer: Buffer[Byte]) => for { @@ -41,13 +32,13 @@ object ScatterGatherChannelSpec extends BaseSpec { } yield assert(list)(equalTo("Hello" :: "World" :: Nil)) } }, - testM("gathering write") { - val file = Path("nio/src/test/resources/gathering_write_test.txt") + test("gathering write") { + val file = Path("nio/shared/src/test/resources/gathering_write_test.txt") FileChannel .open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) - .useNioBlockingOps { ops => + .flatMapNioBlockingOps { ops => for { - buffs <- IO.collectAll( + buffs <- ZIO.collectAll( List( Buffer.byte(Chunk.fromArray("Hello".getBytes)), Buffer.byte(Chunk.fromArray("World".getBytes)) @@ -57,7 +48,7 @@ object ScatterGatherChannelSpec extends BaseSpec { result = Source.fromFile(file.toFile).getLines().toSeq } yield assert(result)(equalTo(Seq("HelloWorld"))) } - .ensuring(IO.effectTotal(Files.delete(file.javaPath))) + .ensuring(ZIO.succeed(Files.delete(file.javaPath))) } ) } diff --git a/nio/src/test/scala/zio/nio/charset/CharsetSpec.scala b/nio/shared/src/test/scala/zio/nio/charset/CharsetSpec.scala similarity index 51% rename from nio/src/test/scala/zio/nio/charset/CharsetSpec.scala rename to nio/shared/src/test/scala/zio/nio/charset/CharsetSpec.scala index 0c7c15fc..a5cb36b2 100644 --- a/nio/src/test/scala/zio/nio/charset/CharsetSpec.scala +++ b/nio/shared/src/test/scala/zio/nio/charset/CharsetSpec.scala @@ -2,52 +2,52 @@ package zio package nio package charset -import zio.stream.Stream +import zio.stream.ZStream import zio.test.Assertion._ -import zio.test.{ZSpec, _} +import zio.test.{Spec, _} import java.nio.charset.{CharacterCodingException, MalformedInputException, UnmappableCharacterException} -object CharsetSpec extends DefaultRunnableSpec { +object CharsetSpec extends ZIOSpecDefault { - override def spec: Spec[Any, TestFailure[CharacterCodingException], TestSuccess] = + override def spec: Spec[Any, CharacterCodingException] = suite("CharsetSpec")( chunkEncodeDecode(Charset.Standard.utf8), chunkEncodeDecode(Charset.Standard.utf16), bufferEncodeDecode(Charset.Standard.utf8), bufferEncodeDecode(Charset.Standard.utf16), - testM("utf8 encode") { + test("utf8 encode") { Charset.Standard.utf8.encodeChunk(arabicChunk).map { assert(_)(equalTo(arabicUtf8)) } }, streamEncodeDecode(Charset.Standard.utf8), streamEncodeDecode(Charset.Standard.utf16Be), - testM("stream decode across chunk boundaries") { - val byteStream = Stream.fromChunks(arabicUtf8.map(Chunk.single): _*) + test("stream decode across chunk boundaries") { + val byteStream = ZStream.fromChunks(arabicUtf8.map(Chunk.single): _*) for { - chars <- byteStream.transduce(Charset.Standard.utf8.newDecoder.transducer()).runCollect + chars <- byteStream.via(Charset.Standard.utf8.newDecoder.transducer()).runCollect } yield assert(chars)(equalTo(arabicChunk)) }, - testM("minimum buffer size for encoding") { - val in = Stream.fromChunk(arabicChunk) + test("minimum buffer size for encoding") { + val in = ZStream.fromChunk(arabicChunk) val t = Charset.Standard.utf8.newEncoder.transducer(49) - assertM(in.transduce(t).runDrain.run)(dies(isSubtype[IllegalArgumentException](anything))) + assertZIO(in.via(t).runDrain.exit)(dies(isSubtype[IllegalArgumentException](anything))) }, - testM("minimum buffer size for decoding") { - val in = Stream.fromChunk(arabicUtf8) + test("minimum buffer size for decoding") { + val in = ZStream.fromChunk(arabicUtf8) val t = Charset.Standard.utf8.newDecoder.transducer(49) - assertM(in.transduce(t).runDrain.run)(dies(isSubtype[IllegalArgumentException](anything))) + assertZIO(in.via(t).runDrain.exit)(dies(isSubtype[IllegalArgumentException](anything))) }, - testM("handles encoding errors") { - val in = Stream.fromChunk(arabicChunk) + test("handles encoding errors") { + val in = ZStream.fromChunk(arabicChunk) val t = Charset.Standard.iso8859_1.newEncoder.transducer() - assertM(in.transduce(t).runDrain.run)(fails(isSubtype[UnmappableCharacterException](anything))) + assertZIO(in.via(t).runDrain.exit)(fails(isSubtype[UnmappableCharacterException](anything))) }, - testM("handles decoding errors") { - val in = Stream(0xd8, 0x00, 0xa5, 0xd8).map(_.toByte) + test("handles decoding errors") { + val in = ZStream(0xd8, 0x00, 0xa5, 0xd8).map(_.toByte) val t = Charset.Standard.utf16Le.newDecoder.transducer() - assertM(in.transduce(t).runDrain.run)(fails(isSubtype[MalformedInputException](anything))) + assertZIO(in.via(t).runDrain.exit)(fails(isSubtype[MalformedInputException](anything))) } ) @@ -59,16 +59,16 @@ object CharsetSpec extends DefaultRunnableSpec { 0xd8, 0xb6, 0xd8, 0xb1, 0xd8, 0xaa, 0xd9, 0x83, 0xd8, 0x9f) .map(_.toByte) - def chunkEncodeDecode(charset: Charset): ZSpec[Any, Nothing] = - testM(s"chunk encode/decode ${charset.displayName}") { + def chunkEncodeDecode(charset: Charset): Spec[Any, Nothing] = + test(s"chunk encode/decode ${charset.displayName}") { for { encoded <- charset.encodeChunk(arabicChunk) decoded <- charset.decodeChunk(encoded) } yield assert(decoded)(equalTo(arabicChunk)) } - def bufferEncodeDecode(charset: Charset): ZSpec[Any, Nothing] = - testM(s"buffer encode/decode ${charset.displayName}") { + def bufferEncodeDecode(charset: Charset): Spec[Any, Nothing] = + test(s"buffer encode/decode ${charset.displayName}") { for { chars <- Buffer.char(100) _ <- chars.putChunk(arabicChunk) @@ -80,13 +80,13 @@ object CharsetSpec extends DefaultRunnableSpec { } yield assert(charsHasRemaining)(isFalse) && assert(chunk)(equalTo(arabicChunk)) } - def streamEncodeDecode(charset: Charset): ZSpec[Any, CharacterCodingException] = - testM(s"stream encode/decode ${charset.displayName}") { - val charStream = Stream.fromIterable(arabic) + def streamEncodeDecode(charset: Charset): Spec[Any, CharacterCodingException] = + test(s"stream encode/decode ${charset.displayName}") { + val charStream = ZStream.fromIterable(arabic) for { - byteChunks <- charStream.transduce(charset.newEncoder.transducer()).runCollect - byteStream = Stream.fromIterable(byteChunks) - chars <- byteStream.transduce(charset.newDecoder.transducer()).runCollect + byteChunks <- charStream.via(charset.newEncoder.transducer()).runCollect + byteStream = ZStream.fromIterable(byteChunks) + chars <- byteStream.via(charset.newDecoder.transducer()).runCollect } yield assert(chars)(equalTo(arabicChunk)) } } diff --git a/nio/shared/src/test/scala/zio/nio/file/FilesSpec.scala b/nio/shared/src/test/scala/zio/nio/file/FilesSpec.scala new file mode 100644 index 00000000..46f76d9c --- /dev/null +++ b/nio/shared/src/test/scala/zio/nio/file/FilesSpec.scala @@ -0,0 +1,104 @@ +package zio.nio.file + +import zio.nio.BaseSpec +import zio.test.Assertion._ +import zio.test._ +import zio.{Chunk, Ref, ZIO} + +object FilesSpec extends BaseSpec { + + override def spec: Spec[Any, Any] = + suite("FilesSpec")( + test("createTempFileInScoped cleans up temp file") { + val sampleFileContent = Chunk.fromArray("createTempFileInScoped works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- ZIO.scoped { + Files + .createTempFileInScoped(dir = Path(".")) + .flatMap { tmpFile => + pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) + } + } + tmpFilePath <- pathRef.get.some + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + test("createTempFileScoped cleans up temp file") { + val sampleFileContent = Chunk.fromArray("createTempFileScoped works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- ZIO.scoped { + Files + .createTempFileScoped() + .flatMap { tmpFile => + pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) + } + } + tmpFilePath <- pathRef.get.some + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + test("createTempDirectoryScoped cleans up temp dir") { + val sampleFileContent = Chunk.fromArray("createTempDirectoryScoped works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- ZIO.scoped { + Files + .createTempDirectoryScoped( + prefix = None, + fileAttributes = Nil + ) + .flatMap { tmpDir => + val sampleFile = tmpDir / "createTempDirectoryScoped" + pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) + } + } + tmpFilePath <- pathRef.get.some + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + test("createTempDirectoryscoped (dir) cleans up temp dir") { + val sampleFileContent = Chunk.fromArray("createTempDirectoryScoped(dir) works!".getBytes) + for { + pathRef <- Ref.make[Option[Path]](None) + readBytes <- ZIO.scoped { + Files + .createTempDirectoryScoped( + dir = Path("."), + prefix = None, + fileAttributes = Nil + ) + .flatMap { tmpDir => + val sampleFile = tmpDir / "createTempDirectoryScoped2" + pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) + } + } + tmpFilePath <- pathRef.get.some + tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) + } yield assert(readBytes)(equalTo(sampleFileContent)) && + assert(tmpFileExistsAfterUsage)(isFalse) + }, + test("deleteRecursive deletes subdirectories") { + for { + outerDir <- Files.createTempDirectory(prefix = None, fileAttributes = Nil) + innerDir = outerDir / "inner" + _ <- Files.createDirectory(innerDir) + //innerDir <- Files.writeLines(innerDir / "file.txt", "test" :: Nil) + _ <- Files.writeLines(innerDir / "file.txt", "test" :: Nil) + _ <- Files.deleteRecursive(outerDir) + isDeleted <- Files.notExists(outerDir) + } yield assertTrue(isDeleted) + + } + ) + + private def createAndWriteAndThenRead(file: Path)(bytes: Chunk[Byte]) = + Files.createFile(file) *> writeAndThenRead(file)(bytes) + + private def writeAndThenRead(file: Path)(bytes: Chunk[Byte]) = + Files.writeBytes(file, bytes) *> Files.readAllBytes(file) +} diff --git a/nio/src/test/scala/zio/nio/file/PathSpec.scala b/nio/shared/src/test/scala/zio/nio/file/PathSpec.scala similarity index 86% rename from nio/src/test/scala/zio/nio/file/PathSpec.scala rename to nio/shared/src/test/scala/zio/nio/file/PathSpec.scala index c7e19629..9966219f 100644 --- a/nio/src/test/scala/zio/nio/file/PathSpec.scala +++ b/nio/shared/src/test/scala/zio/nio/file/PathSpec.scala @@ -3,10 +3,11 @@ package zio.nio.file import zio.nio.BaseSpec import zio.test.Assertion._ import zio.test._ +import zio.Scope object PathSpec extends BaseSpec { - override def spec: ZSpec[Environment, Failure] = + override def spec: Spec[TestEnvironment with Scope, Any] = suite("PathSpec")( test("Path construction") { val p = Path("a", "b") / "c/d" diff --git a/nio/src/main/scala-2/zio/nio/channels/package.scala b/nio/src/main/scala-2/zio/nio/channels/package.scala deleted file mode 100644 index dbe18ab7..00000000 --- a/nio/src/main/scala-2/zio/nio/channels/package.scala +++ /dev/null @@ -1,36 +0,0 @@ -package zio.nio - -import zio.blocking.Blocking -import zio.{ZIO, ZManaged} - -import java.io.IOException - -package object channels { - - implicit final class ManagedBlockingNioOps[-R, +C <: BlockingChannel]( - private val underlying: ZManaged[R, IOException, C] - ) extends AnyVal { - - def useNioBlocking[R1, E >: IOException, A]( - f: (C, C#BlockingOps) => ZIO[R1, E, A] - ): ZIO[R with R1 with Blocking, E, A] = underlying.use(c => c.useBlocking(f(c, _))) - - def useNioBlockingOps[R1, E >: IOException, A]( - f: C#BlockingOps => ZIO[R1, E, A] - ): ZIO[R with R1 with Blocking, E, A] = useNioBlocking((_, ops) => f(ops)) - - } - - implicit final class ManagedNonBlockingNioOps[-R, +C <: SelectableChannel]( - private val underlying: ZManaged[R, IOException, C] - ) extends AnyVal { - - def useNioNonBlocking[R1, E >: IOException, A](f: (C, C#NonBlockingOps) => ZIO[R1, E, A]): ZIO[R with R1, E, A] = - underlying.use(c => c.useNonBlocking(f(c, _))) - - def useNioNonBlockingOps[R1, E >: IOException, A](f: C#NonBlockingOps => ZIO[R1, E, A]): ZIO[R with R1, E, A] = - useNioNonBlocking((_, ops) => f(ops)) - - } - -} diff --git a/nio/src/main/scala-3/zio/nio/channels/package.scala b/nio/src/main/scala-3/zio/nio/channels/package.scala deleted file mode 100644 index 9d65b925..00000000 --- a/nio/src/main/scala-3/zio/nio/channels/package.scala +++ /dev/null @@ -1,37 +0,0 @@ -package zio.nio - -import zio.blocking.Blocking -import zio.{ ZIO, ZManaged } - -import java.io.IOException - -package object channels { - - implicit final class ManagedBlockingNioOps[-R, BO, C <: BlockingChannel { type BlockingOps <: BO }]( - private val underlying: ZManaged[R, IOException, C] - ) extends AnyVal { - type F1[R, E, A] = (C, BO) => ZIO[R, E, A] - - def useNioBlocking[R1, E >: IOException, A]( - f: F1[R1, E, A] - ): ZIO[R with R1 with Blocking, E, A] = underlying.use(c => c.useBlocking(f(c, _))) - - def useNioBlockingOps[R1, E >: IOException, A]( - f: BO => ZIO[R1, E, A] - ): ZIO[R with R1 with Blocking, E, A] = useNioBlocking((_, ops) => f(ops)) - - } - - implicit final class ManagedNonBlockingNioOps[-R, BO, C <: SelectableChannel { type NonBlockingOps <: BO }]( - private val underlying: ZManaged[R, IOException, C] - ) extends AnyVal { - - def useNioNonBlocking[R1, E >: IOException, A](f: (C, BO) => ZIO[R1, E, A]): ZIO[R with R1, E, A] = - underlying.use(c => c.useNonBlocking(f(c, _))) - - def useNioNonBlockingOps[R1, E >: IOException, A](f: BO => ZIO[R1, E, A]): ZIO[R with R1, E, A] = - useNioNonBlocking((_, ops) => f(ops)) - - } - -} diff --git a/nio/src/main/scala/zio/nio/CharBuffer.scala b/nio/src/main/scala/zio/nio/CharBuffer.scala deleted file mode 100644 index abeb46d1..00000000 --- a/nio/src/main/scala/zio/nio/CharBuffer.scala +++ /dev/null @@ -1,57 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, CharBuffer => JCharBuffer} - -/** - * A mutable buffer of characters. - */ -final class CharBuffer(protected[nio] val buffer: JCharBuffer) extends Buffer[Char] { - - override protected[nio] def array: UIO[Array[Char]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order()) - - override def slice: UIO[CharBuffer] = UIO.effectTotal(new CharBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[CharBuffer] = UIO.effectTotal(new CharBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java character buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java character buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JCharBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Char] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Char] = UIO.effectTotal(buffer.get(i)) - - override def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[Char]] = - UIO.effectTotal { - val array = Array.ofDim[Char](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - def getString: UIO[String] = UIO.effectTotal(buffer.toString()) - - override def put(element: Char): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Char): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Char]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[CharBuffer] = UIO.effectTotal(new CharBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/DoubleBuffer.scala b/nio/src/main/scala/zio/nio/DoubleBuffer.scala deleted file mode 100644 index 413a0d3f..00000000 --- a/nio/src/main/scala/zio/nio/DoubleBuffer.scala +++ /dev/null @@ -1,57 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, DoubleBuffer => JDoubleBuffer} - -/** - * A mutable buffer of doubles. - */ -final class DoubleBuffer(protected[nio] val buffer: JDoubleBuffer) extends Buffer[Double] { - - override protected[nio] def array: UIO[Array[Double]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order) - - override def slice: UIO[DoubleBuffer] = UIO.effectTotal(new DoubleBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[DoubleBuffer] = UIO.effectTotal(new DoubleBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java double buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java double buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JDoubleBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Double] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Double] = UIO.effectTotal(buffer.get(i)) - - override def getChunk( - maxLength: Int = Int.MaxValue - ): UIO[Chunk[Double]] = - UIO.effectTotal { - val array = Array.ofDim[Double](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - override def put(element: Double): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Double): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Double]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[DoubleBuffer] = UIO.effectTotal(new DoubleBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/FloatBuffer.scala b/nio/src/main/scala/zio/nio/FloatBuffer.scala deleted file mode 100644 index 86fd4cf9..00000000 --- a/nio/src/main/scala/zio/nio/FloatBuffer.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, FloatBuffer => JFloatBuffer} - -/** - * A mutable buffer of floats. - */ -final class FloatBuffer(protected[nio] val buffer: JFloatBuffer) extends Buffer[Float] { - - override protected[nio] def array: UIO[Array[Float]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order) - - override def slice: UIO[FloatBuffer] = UIO.effectTotal(new FloatBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[FloatBuffer] = UIO.effectTotal(new FloatBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java float buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java float buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JFloatBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Float] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Float] = UIO.effectTotal(buffer.get(i)) - - override def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[Float]] = - UIO.effectTotal { - val array = Array.ofDim[Float](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - override def put(element: Float): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Float): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Float]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[FloatBuffer] = UIO.effectTotal(new FloatBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/IntBuffer.scala b/nio/src/main/scala/zio/nio/IntBuffer.scala deleted file mode 100644 index 65a6e6d2..00000000 --- a/nio/src/main/scala/zio/nio/IntBuffer.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, IntBuffer => JIntBuffer} - -/** - * A mutable buffer of ints. - */ -final class IntBuffer(protected[nio] val buffer: JIntBuffer) extends Buffer[Int] { - - override protected[nio] def array: UIO[Array[Int]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order) - - override def slice: UIO[IntBuffer] = UIO.effectTotal(new IntBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[IntBuffer] = UIO.effectTotal(new IntBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java int buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java int buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JIntBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Int] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Int] = UIO.effectTotal(buffer.get(i)) - - override def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[Int]] = - UIO.effectTotal { - val array = Array.ofDim[Int](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - override def put(element: Int): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Int): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Int]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[IntBuffer] = UIO.effectTotal(new IntBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/LongBuffer.scala b/nio/src/main/scala/zio/nio/LongBuffer.scala deleted file mode 100644 index 4aed3cf9..00000000 --- a/nio/src/main/scala/zio/nio/LongBuffer.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, LongBuffer => JLongBuffer} - -/** - * A mutable buffer of longs. - */ -final class LongBuffer(protected[nio] val buffer: JLongBuffer) extends Buffer[Long] { - - override protected[nio] def array: UIO[Array[Long]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order) - - override def slice: UIO[LongBuffer] = UIO.effectTotal(new LongBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[LongBuffer] = UIO.effectTotal(new LongBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java long buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java long buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JLongBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Long] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Long] = UIO.effectTotal(buffer.get(i)) - - override def getChunk(maxLength: Int = Int.MaxValue): UIO[Chunk[Long]] = - UIO.effectTotal { - val array = Array.ofDim[Long](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - override def put(element: Long): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Long): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Long]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[LongBuffer] = UIO.effectTotal(new LongBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/NetworkInterface.scala b/nio/src/main/scala/zio/nio/NetworkInterface.scala deleted file mode 100644 index 0d8a9512..00000000 --- a/nio/src/main/scala/zio/nio/NetworkInterface.scala +++ /dev/null @@ -1,74 +0,0 @@ -package zio.nio - -import com.github.ghik.silencer.silent -import zio.IO - -import java.net.{NetworkInterface => JNetworkInterface, SocketException} -import scala.collection.JavaConverters._ - -class NetworkInterface private[nio] (private[nio] val jNetworkInterface: JNetworkInterface) { - - def name: String = jNetworkInterface.getName - - @silent - def inetAddresses: List[InetAddress] = jNetworkInterface.getInetAddresses.asScala.map(new InetAddress(_)).toList - - @silent - def interfaceAddresses: List[InterfaceAddress] = - jNetworkInterface.getInterfaceAddresses.asScala.map(new InterfaceAddress(_)).toList - - @silent - def subInterfaces: Iterator[NetworkInterface] = - jNetworkInterface.getSubInterfaces.asScala.map(new NetworkInterface(_)) - - def parent: NetworkInterface = new NetworkInterface(jNetworkInterface.getParent) - - def index: Int = jNetworkInterface.getIndex - - def displayName: String = jNetworkInterface.getDisplayName - - val isUp: IO[SocketException, Boolean] = - IO.effect(jNetworkInterface.isUp).refineToOrDie[SocketException] - - val isLoopback: IO[SocketException, Boolean] = - IO.effect(jNetworkInterface.isLoopback).refineToOrDie[SocketException] - - val isPointToPoint: IO[SocketException, Boolean] = - IO.effect(jNetworkInterface.isPointToPoint).refineToOrDie[SocketException] - - val supportsMulticast: IO[SocketException, Boolean] = - IO.effect(jNetworkInterface.supportsMulticast).refineToOrDie[SocketException] - - val hardwareAddress: IO[SocketException, Array[Byte]] = - IO.effect(jNetworkInterface.getHardwareAddress).refineToOrDie[SocketException] - - val mtu: IO[SocketException, Int] = - IO.effect(jNetworkInterface.getMTU).refineToOrDie[SocketException] - - def isVirtual: Boolean = jNetworkInterface.isVirtual -} - -object NetworkInterface { - - def byName(name: String): IO[SocketException, NetworkInterface] = - IO.effect(JNetworkInterface.getByName(name)) - .refineToOrDie[SocketException] - .map(new NetworkInterface(_)) - - def byIndex(index: Integer): IO[SocketException, NetworkInterface] = - IO.effect(JNetworkInterface.getByIndex(index)) - .refineToOrDie[SocketException] - .map(new NetworkInterface(_)) - - def byInetAddress(address: InetAddress): IO[SocketException, NetworkInterface] = - IO.effect(JNetworkInterface.getByInetAddress(address.jInetAddress)) - .refineToOrDie[SocketException] - .map(new NetworkInterface(_)) - - @silent - def networkInterfaces: IO[SocketException, Iterator[NetworkInterface]] = - IO.effect(JNetworkInterface.getNetworkInterfaces.asScala) - .refineToOrDie[SocketException] - .map(_.map(new NetworkInterface(_))) - -} diff --git a/nio/src/main/scala/zio/nio/ShortBuffer.scala b/nio/src/main/scala/zio/nio/ShortBuffer.scala deleted file mode 100644 index ff671a6d..00000000 --- a/nio/src/main/scala/zio/nio/ShortBuffer.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio - -import zio.{Chunk, UIO, ZIO} - -import java.nio.{ByteOrder, ShortBuffer => JShortBuffer} - -/** - * A mutable buffer of shorts. - */ -final class ShortBuffer(protected[nio] val buffer: JShortBuffer) extends Buffer[Short] { - - override protected[nio] def array: UIO[Array[Short]] = UIO.effectTotal(buffer.array()) - - override def order: UIO[ByteOrder] = UIO.effectTotal(buffer.order()) - - override def slice: UIO[ShortBuffer] = UIO.effectTotal(new ShortBuffer(buffer.slice())) - - override def compact: UIO[Unit] = UIO.effectTotal(buffer.compact()).unit - - override def duplicate: UIO[ShortBuffer] = UIO.effectTotal(new ShortBuffer(buffer.duplicate())) - - /** - * Provides the underlying Java short buffer for use in an effect. - * - * This is useful when using Java APIs that require a Java short buffer to be provided. - * - * @return - * The effect value constructed by `f` using the underlying buffer. - */ - def withJavaBuffer[R, E, A](f: JShortBuffer => ZIO[R, E, A]): ZIO[R, E, A] = f(buffer) - - override def get: UIO[Short] = UIO.effectTotal(buffer.get()) - - override def get(i: Int): UIO[Short] = UIO.effectTotal(buffer.get(i)) - - override def getChunk(maxLength: Int): UIO[Chunk[Short]] = - UIO.effectTotal { - val array = Array.ofDim[Short](math.min(maxLength, buffer.remaining())) - buffer.get(array) - Chunk.fromArray(array) - } - - override def put(element: Short): UIO[Unit] = UIO.effectTotal(buffer.put(element)).unit - - override def put(index: Int, element: Short): UIO[Unit] = UIO.effectTotal(buffer.put(index, element)).unit - - override protected def putChunkAll(chunk: Chunk[Short]): UIO[Unit] = - UIO.effectTotal { - val array = chunk.toArray - buffer.put(array) - }.unit - - override def asReadOnlyBuffer: UIO[ShortBuffer] = UIO.effectTotal(new ShortBuffer(buffer.asReadOnlyBuffer())) - -} diff --git a/nio/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala b/nio/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala deleted file mode 100644 index 4a5332f8..00000000 --- a/nio/src/main/scala/zio/nio/channels/AsynchronousChannelGroup.scala +++ /dev/null @@ -1,55 +0,0 @@ -package zio.nio.channels - -import zio.duration._ -import zio.{IO, UIO} - -import java.io.IOException -import java.nio.channels.spi.{AsynchronousChannelProvider => JAsynchronousChannelProvider} -import java.nio.channels.{AsynchronousChannelGroup => JAsynchronousChannelGroup} -import java.util.concurrent.{ThreadFactory => JThreadFactory, TimeUnit} -import scala.concurrent.ExecutionContextExecutorService - -object AsynchronousChannelGroup { - - def apply(executor: ExecutionContextExecutorService, initialSize: Int): IO[IOException, AsynchronousChannelGroup] = - IO.effect( - new AsynchronousChannelGroup( - JAsynchronousChannelGroup.withCachedThreadPool(executor, initialSize) - ) - ).refineToOrDie[IOException] - - def apply( - threadsNo: Int, - threadsFactory: JThreadFactory - ): IO[IOException, AsynchronousChannelGroup] = - IO.effect( - new AsynchronousChannelGroup( - JAsynchronousChannelGroup.withFixedThreadPool(threadsNo, threadsFactory) - ) - ).refineToOrDie[IOException] - - def apply(executor: ExecutionContextExecutorService): IO[IOException, AsynchronousChannelGroup] = - IO.effect( - new AsynchronousChannelGroup(JAsynchronousChannelGroup.withThreadPool(executor)) - ).refineToOrDie[IOException] - -} - -final class AsynchronousChannelGroup(val channelGroup: JAsynchronousChannelGroup) { - - def awaitTermination(timeout: Duration): IO[InterruptedException, Boolean] = - IO.effect(channelGroup.awaitTermination(timeout.asJava.toMillis, TimeUnit.MILLISECONDS)) - .refineToOrDie[InterruptedException] - - val isShutdown: UIO[Boolean] = IO.effectTotal(channelGroup.isShutdown) - - val isTerminated: UIO[Boolean] = IO.effectTotal(channelGroup.isTerminated) - - val provider: UIO[JAsynchronousChannelProvider] = IO.effectTotal(channelGroup.provider()) - - val shutdown: UIO[Unit] = IO.effectTotal(channelGroup.shutdown()) - - val shutdownNow: IO[IOException, Unit] = - IO.effect(channelGroup.shutdownNow()).refineToOrDie[IOException] - -} diff --git a/nio/src/main/scala/zio/nio/channels/SelectableChannel.scala b/nio/src/main/scala/zio/nio/channels/SelectableChannel.scala deleted file mode 100644 index 4dd90643..00000000 --- a/nio/src/main/scala/zio/nio/channels/SelectableChannel.scala +++ /dev/null @@ -1,254 +0,0 @@ -package zio.nio -package channels - -import zio.blocking.Blocking -import zio.nio.channels.SelectionKey.Operation -import zio.nio.channels.spi.SelectorProvider -import zio.{Fiber, IO, Managed, UIO, ZIO, ZManaged} - -import java.io.IOException -import java.net.{ServerSocket => JServerSocket, Socket => JSocket, SocketOption} -import java.nio.channels.{ - ClosedChannelException, - SelectableChannel => JSelectableChannel, - ServerSocketChannel => JServerSocketChannel, - SocketChannel => JSocketChannel -} - -/** - * A channel that can be multiplexed via a [[zio.nio.channels.Selector]]. - */ -trait SelectableChannel extends BlockingChannel { - - /** - * The non-blocking operations supported by this channel. - */ - type NonBlockingOps - - protected val channel: JSelectableChannel - - final val provider: UIO[SelectorProvider] = - IO.effectTotal(new SelectorProvider(channel.provider())) - - final val validOps: UIO[Set[Operation]] = - IO.effectTotal(channel.validOps()) - .map(Operation.fromInt(_)) - - final val isRegistered: UIO[Boolean] = - IO.effectTotal(channel.isRegistered()) - - final def keyFor(sel: Selector): UIO[Option[SelectionKey]] = - IO.effectTotal(Option(channel.keyFor(sel.selector)).map(new SelectionKey(_))) - - /** - * Registers this channel with the given selector, returning a selection key. - * - * @param selector - * The selector to register with. - * @param ops - * The key's interest set will be created with these operations. - * @param attachment - * The object to attach to the key, if any. - * @return - * The new `SelectionKey`. - */ - final def register( - selector: Selector, - ops: Set[Operation] = Set.empty, - attachment: Option[AnyRef] = None - ): IO[ClosedChannelException, SelectionKey] = - IO.effect(new SelectionKey(channel.register(selector.selector, Operation.toInt(ops), attachment.orNull))) - .refineToOrDie[ClosedChannelException] - - final def configureBlocking(block: Boolean): IO[IOException, Unit] = - IO.effect(channel.configureBlocking(block)).unit.refineToOrDie[IOException] - - final val isBlocking: UIO[Boolean] = - IO.effectTotal(channel.isBlocking()) - - final val blockingLock: UIO[AnyRef] = - IO.effectTotal(channel.blockingLock()) - - protected def makeBlockingOps: BlockingOps - - final override def useBlocking[R, E >: IOException, A](f: BlockingOps => ZIO[R, E, A]): ZIO[R with Blocking, E, A] = - configureBlocking(true) *> nioBlocking(f(makeBlockingOps)) - - protected def makeNonBlockingOps: NonBlockingOps - - /** - * Puts this channel into non-blocking mode and performs a set of non-blocking operations. - * - * @param f - * Uses the `NonBlockingOps` appropriate for this channel type to produce non-blocking effects. - */ - final def useNonBlocking[R, E >: IOException, A](f: NonBlockingOps => ZIO[R, E, A]): ZIO[R, E, A] = - configureBlocking(false) *> f(makeNonBlockingOps) - - /** - * Puts this channel into non-blocking mode and performs a set of non-blocking operations as a managed resource. - * - * @param f - * Uses the `NonBlockingOps` appropriate for this channel type to produce non-blocking effects. - */ - final def useNonBlockingManaged[R, E >: IOException, A](f: NonBlockingOps => ZManaged[R, E, A]): ZManaged[R, E, A] = - configureBlocking(false).toManaged_ *> f(makeNonBlockingOps) - -} - -final class SocketChannel(override protected[channels] val channel: JSocketChannel) extends SelectableChannel { - - self => - - override type BlockingOps = BlockingSocketOps - - override type NonBlockingOps = NonBlockingSocketOps - - sealed abstract class Ops extends GatheringByteOps with ScatteringByteOps { - override protected[channels] def channel = self.channel - } - - final class BlockingSocketOps private[SocketChannel] () extends Ops { - - def connect(remote: SocketAddress): IO[IOException, Unit] = - IO.effect(self.channel.connect(remote.jSocketAddress)).refineToOrDie[IOException].unit - - } - - override protected def makeBlockingOps = new BlockingSocketOps - - final class NonBlockingSocketOps private[SocketChannel] () extends Ops { - - def isConnectionPending: UIO[Boolean] = IO.effectTotal(self.channel.isConnectionPending) - - def connect(remote: SocketAddress): IO[IOException, Boolean] = - IO.effect(self.channel.connect(remote.jSocketAddress)).refineToOrDie[IOException] - - def finishConnect: IO[IOException, Boolean] = IO.effect(self.channel.finishConnect()).refineToOrDie[IOException] - - } - - override protected def makeNonBlockingOps = new NonBlockingSocketOps - - def bindTo(address: SocketAddress): IO[IOException, Unit] = bind(Some(address)) - - def bindAuto: IO[IOException, Unit] = bind(None) - - def bind(local: Option[SocketAddress]): IO[IOException, Unit] = - IO.effect(channel.bind(local.map(_.jSocketAddress).orNull)).refineToOrDie[IOException].unit - - def setOption[T](name: SocketOption[T], value: T): IO[IOException, Unit] = - IO.effect(channel.setOption(name, value)).refineToOrDie[IOException].unit - - def shutdownInput: IO[IOException, Unit] = IO.effect(channel.shutdownInput()).refineToOrDie[IOException].unit - - def shutdownOutput: IO[IOException, Unit] = IO.effect(channel.shutdownOutput()).refineToOrDie[IOException].unit - - def socket: UIO[JSocket] = IO.effectTotal(channel.socket()) - - def isConnected: UIO[Boolean] = IO.effectTotal(channel.isConnected) - - def remoteAddress: IO[IOException, SocketAddress] = - IO.effect(SocketAddress.fromJava(channel.getRemoteAddress())).refineToOrDie[IOException] - - def localAddress: IO[IOException, Option[SocketAddress]] = - IO.effect(Option(channel.getLocalAddress()).map(SocketAddress.fromJava)) - .refineToOrDie[IOException] - -} - -object SocketChannel { - - def fromJava(javaSocketChannel: JSocketChannel): SocketChannel = new SocketChannel(javaSocketChannel) - - val open: Managed[IOException, SocketChannel] = - IO.effect(new SocketChannel(JSocketChannel.open())).refineToOrDie[IOException].toNioManaged - - def open(remote: SocketAddress): Managed[IOException, SocketChannel] = - IO.effect(new SocketChannel(JSocketChannel.open(remote.jSocketAddress))).refineToOrDie[IOException].toNioManaged - -} - -final class ServerSocketChannel(override protected val channel: JServerSocketChannel) extends SelectableChannel { - - override type BlockingOps = BlockingServerSocketOps - - override type NonBlockingOps = NonBlockingServerSocketOps - - final class BlockingServerSocketOps private[ServerSocketChannel] () { - - /** - * Accepts a socket connection. - * - * Note that the accept operation is not performed until the returned managed resource is actually used. - * `Managed.preallocate` can be used to preform the accept immediately. - * - * @return - * The channel for the accepted socket connection. - */ - def accept: Managed[IOException, SocketChannel] = - IO.effect(new SocketChannel(channel.accept())).refineToOrDie[IOException].toNioManaged - - /** - * Accepts a connection and uses it to perform an effect on a forked fiber. - * - * @param use - * Uses the accepted socket channel to produce an effect value, which will be run on a forked fiber. - * @return - * The fiber running the effect. - */ - def acceptAndFork[R, A]( - use: SocketChannel => ZIO[R, IOException, A] - ): ZIO[R, IOException, Fiber[IOException, A]] = accept.useForked(use) - - } - - override protected def makeBlockingOps: BlockingServerSocketOps = new BlockingServerSocketOps - - final class NonBlockingServerSocketOps private[ServerSocketChannel] () { - - /** - * Accepts a socket connection. - * - * Note that the accept operation is not performed until the returned managed resource is actually used. - * `Managed.preallocate` can be used to preform the accept immediately. - * - * @return - * None if no connection is currently available to be accepted. - */ - def accept: Managed[IOException, Option[SocketChannel]] = - IO.effect(Option(channel.accept()).map(new SocketChannel(_))) - .refineToOrDie[IOException] - .toManaged(IO.whenCase(_) { case Some(channel) => - channel.close.ignore - }) - - } - - override protected def makeNonBlockingOps: NonBlockingServerSocketOps = new NonBlockingServerSocketOps - - def bindTo(local: SocketAddress, backlog: Int = 0): IO[IOException, Unit] = bind(Some(local), backlog) - - def bindAuto(backlog: Int = 0): IO[IOException, Unit] = bind(None, backlog) - - def bind(local: Option[SocketAddress], backlog: Int = 0): IO[IOException, Unit] = - IO.effect(channel.bind(local.map(_.jSocketAddress).orNull, backlog)).refineToOrDie[IOException].unit - - def setOption[T](name: SocketOption[T], value: T): IO[IOException, Unit] = - IO.effect(channel.setOption(name, value)).refineToOrDie[IOException].unit - - val socket: UIO[JServerSocket] = - IO.effectTotal(channel.socket()) - - val localAddress: IO[IOException, SocketAddress] = - IO.effect(SocketAddress.fromJava(channel.getLocalAddress())).refineToOrDie[IOException] - -} - -object ServerSocketChannel { - - val open: Managed[IOException, ServerSocketChannel] = - IO.effect(new ServerSocketChannel(JServerSocketChannel.open())).refineToOrDie[IOException].toNioManaged - - def fromJava(javaChannel: JServerSocketChannel): ServerSocketChannel = new ServerSocketChannel(javaChannel) -} diff --git a/nio/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala b/nio/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala deleted file mode 100644 index f544226e..00000000 --- a/nio/src/main/scala/zio/nio/channels/spi/SelectorProvider.scala +++ /dev/null @@ -1,53 +0,0 @@ -package zio.nio.channels.spi - -import zio.IO -import zio.nio.channels._ - -import java.io.IOException -import java.net.ProtocolFamily -import java.nio.channels.spi.{SelectorProvider => JSelectorProvider} -import java.nio.{channels => jc} - -final class SelectorProvider(private val selectorProvider: JSelectorProvider) { - - val openDatagramChannel: IO[IOException, DatagramChannel] = - IO.effect(DatagramChannel.fromJava(selectorProvider.openDatagramChannel())).refineToOrDie[IOException] - - // this can throw UnsupportedOperationException - doesn't seem like a recoverable exception - def openDatagramChannel( - family: ProtocolFamily - ): IO[IOException, DatagramChannel] = - IO.effect(DatagramChannel.fromJava(selectorProvider.openDatagramChannel(family))).refineToOrDie[IOException] - - val openPipe: IO[IOException, Pipe] = - IO.effect(Pipe.fromJava(selectorProvider.openPipe())).refineToOrDie[IOException] - - val openSelector: IO[IOException, Selector] = - IO.effect(new Selector(selectorProvider.openSelector())).refineToOrDie[IOException] - - val openServerSocketChannel: IO[IOException, ServerSocketChannel] = - IO.effect(ServerSocketChannel.fromJava(selectorProvider.openServerSocketChannel())).refineToOrDie[IOException] - - val openSocketChannel: IO[IOException, SocketChannel] = - IO.effect(new SocketChannel(selectorProvider.openSocketChannel())).refineToOrDie[IOException] - - val inheritedChannel: IO[IOException, Option[Channel]] = - IO.effect(Option(selectorProvider.inheritedChannel())) - .map { - _.collect { - case c: jc.SocketChannel => SocketChannel.fromJava(c) - case c: jc.ServerSocketChannel => ServerSocketChannel.fromJava(c) - case c: jc.DatagramChannel => DatagramChannel.fromJava(c) - case c: jc.FileChannel => FileChannel.fromJava(c) - } - } - .refineToOrDie[IOException] - -} - -object SelectorProvider { - - final def default: IO[Nothing, SelectorProvider] = - IO.effectTotal(JSelectorProvider.provider()).map(new SelectorProvider(_)) - -} diff --git a/nio/src/main/scala/zio/nio/file/Files.scala b/nio/src/main/scala/zio/nio/file/Files.scala deleted file mode 100644 index 285ebc09..00000000 --- a/nio/src/main/scala/zio/nio/file/Files.scala +++ /dev/null @@ -1,362 +0,0 @@ -package zio.nio.file - -import zio.blocking._ -import zio.nio.charset.Charset -import zio.stream.{ZSink, ZStream} -import zio.{Chunk, ZIO, ZManaged} - -import java.io.IOException -import java.nio.file.attribute._ -import java.nio.file.{ - CopyOption, - DirectoryStream, - FileStore, - FileVisitOption, - Files => JFiles, - LinkOption, - OpenOption, - Path => JPath -} -import java.util.function.BiPredicate -import scala.jdk.CollectionConverters._ -import scala.reflect._ - -object Files { - - def newDirectoryStream(dir: Path, glob: String = "*"): ZStream[Blocking, IOException, Path] = { - val managed = ZManaged - .fromAutoCloseable(effectBlocking(JFiles.newDirectoryStream(dir.javaPath, glob))) - .map(_.iterator()) - ZStream.fromJavaIteratorManaged(managed).map(Path.fromJava).refineToOrDie[IOException] - } - - def newDirectoryStream(dir: Path, filter: Path => Boolean): ZStream[Blocking, IOException, Path] = { - val javaFilter: DirectoryStream.Filter[_ >: JPath] = javaPath => filter(Path.fromJava(javaPath)) - val managed = ZManaged - .fromAutoCloseable(effectBlocking(JFiles.newDirectoryStream(dir.javaPath, javaFilter))) - .map(_.iterator()) - ZStream.fromJavaIteratorManaged(managed).map(Path.fromJava).refineToOrDie[IOException] - } - - def createFile(path: Path, attrs: FileAttribute[_]*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.createFile(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] - - def createDirectory(path: Path, attrs: FileAttribute[_]*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.createDirectory(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] - - def createDirectories(path: Path, attrs: FileAttribute[_]*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.createDirectories(path.javaPath, attrs: _*)).unit.refineToOrDie[IOException] - - def createTempFileIn( - dir: Path, - suffix: String = ".tmp", - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZIO[Blocking, IOException, Path] = - effectBlocking(Path.fromJava(JFiles.createTempFile(dir.javaPath, prefix.orNull, suffix, fileAttributes.toSeq: _*))) - .refineToOrDie[IOException] - - def createTempFileInManaged( - dir: Path, - suffix: String = ".tmp", - prefix: Option[String] = None, - fileAttributes: Iterable[FileAttribute[_]] = Nil - ): ZManaged[Blocking, IOException, Path] = - ZManaged.make(createTempFileIn(dir, suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) - - def createTempFile( - suffix: String = ".tmp", - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZIO[Blocking, IOException, Path] = - effectBlocking(Path.fromJava(JFiles.createTempFile(prefix.orNull, suffix, fileAttributes.toSeq: _*))) - .refineToOrDie[IOException] - - def createTempFileManaged( - suffix: String = ".tmp", - prefix: Option[String] = None, - fileAttributes: Iterable[FileAttribute[_]] = Nil - ): ZManaged[Blocking, IOException, Path] = - ZManaged.make(createTempFile(suffix, prefix, fileAttributes))(release = deleteIfExists(_).ignore) - - def createTempDirectory( - dir: Path, - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZIO[Blocking, IOException, Path] = - effectBlocking(Path.fromJava(JFiles.createTempDirectory(dir.javaPath, prefix.orNull, fileAttributes.toSeq: _*))) - .refineToOrDie[IOException] - - def createTempDirectoryManaged( - dir: Path, - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZManaged[Blocking, IOException, Path] = - ZManaged.make(createTempDirectory(dir, prefix, fileAttributes))(release = deleteRecursive(_).ignore) - - def createTempDirectory( - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZIO[Blocking, IOException, Path] = - effectBlocking(Path.fromJava(JFiles.createTempDirectory(prefix.orNull, fileAttributes.toSeq: _*))) - .refineToOrDie[IOException] - - def createTempDirectoryManaged( - prefix: Option[String], - fileAttributes: Iterable[FileAttribute[_]] - ): ZManaged[Blocking, IOException, Path] = - ZManaged.make(createTempDirectory(prefix, fileAttributes))(release = deleteRecursive(_).ignore) - - def createSymbolicLink( - link: Path, - target: Path, - fileAttributes: FileAttribute[_]* - ): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.createSymbolicLink(link.javaPath, target.javaPath, fileAttributes: _*)).unit - .refineToOrDie[IOException] - - def createLink(link: Path, existing: Path): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.createLink(link.javaPath, existing.javaPath)).unit.refineToOrDie[IOException] - - def delete(path: Path): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.delete(path.javaPath)).refineToOrDie[IOException] - - def deleteIfExists(path: Path): ZIO[Blocking, IOException, Boolean] = - effectBlocking(JFiles.deleteIfExists(path.javaPath)).refineToOrDie[IOException] - - def deleteRecursive(path: Path): ZIO[Blocking, IOException, Long] = - newDirectoryStream(path).mapM(delete).run(ZSink.count) <* delete(path) - - def copy(source: Path, target: Path, copyOptions: CopyOption*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.copy(source.javaPath, target.javaPath, copyOptions: _*)).unit - .refineToOrDie[IOException] - - def move(source: Path, target: Path, copyOptions: CopyOption*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.move(source.javaPath, target.javaPath, copyOptions: _*)).unit.refineToOrDie[IOException] - - def readSymbolicLink(link: Path): ZIO[Blocking, IOException, Path] = - effectBlocking(Path.fromJava(JFiles.readSymbolicLink(link.javaPath))).refineToOrDie[IOException] - - def getFileStore(path: Path): ZIO[Blocking, IOException, FileStore] = - effectBlocking(JFiles.getFileStore(path.javaPath)).refineToOrDie[IOException] - - def isSameFile(path: Path, path2: Path): ZIO[Blocking, IOException, Boolean] = - effectBlocking(JFiles.isSameFile(path.javaPath, path2.javaPath)).refineToOrDie[IOException] - - def isHidden(path: Path): ZIO[Blocking, IOException, Boolean] = - effectBlocking(JFiles.isHidden(path.javaPath)).refineToOrDie[IOException] - - def probeContentType(path: Path): ZIO[Blocking, IOException, String] = - effectBlocking(JFiles.probeContentType(path.javaPath)).refineToOrDie[IOException] - - def useFileAttributeView[A <: FileAttributeView: ClassTag, B, E](path: Path, linkOptions: LinkOption*)( - f: A => ZIO[Blocking, E, B] - ): ZIO[Blocking, E, B] = { - val viewClass = - classTag[A].runtimeClass.asInstanceOf[Class[A]] // safe? because we know A is a subtype of FileAttributeView - effectBlocking(JFiles.getFileAttributeView[A](path.javaPath, viewClass, linkOptions: _*)).orDie - .flatMap(f) - } - - def readAttributes[A <: BasicFileAttributes: ClassTag]( - path: Path, - linkOptions: LinkOption* - ): ZIO[Blocking, IOException, A] = { - // safe? because we know A is a subtype of BasicFileAttributes - val attributeClass = classTag[A].runtimeClass.asInstanceOf[Class[A]] - effectBlocking(JFiles.readAttributes(path.javaPath, attributeClass, linkOptions: _*)) - .refineToOrDie[IOException] - } - - final case class Attribute(attributeName: String, viewName: String = "basic") { - def toJava: String = s"$viewName:$attributeName" - } - - object Attribute { - - def fromJava(javaAttribute: String): Option[Attribute] = - javaAttribute.split(':').toList match { - case name :: Nil => Some(Attribute(name)) - case view :: name :: Nil => Some(Attribute(name, view)) - case _ => None - } - - } - - def setAttribute( - path: Path, - attribute: Attribute, - value: Object, - linkOptions: LinkOption* - ): ZIO[Blocking, Exception, Unit] = - effectBlocking(JFiles.setAttribute(path.javaPath, attribute.toJava, value, linkOptions: _*)).unit - .refineToOrDie[Exception] - - def getAttribute(path: Path, attribute: Attribute, linkOptions: LinkOption*): ZIO[Blocking, IOException, Object] = - effectBlocking(JFiles.getAttribute(path.javaPath, attribute.toJava, linkOptions: _*)).refineToOrDie[IOException] - - sealed trait AttributeNames { - - def toJava: String = - this match { - case AttributeNames.All => "*" - case AttributeNames.List(names) => names.mkString(",") - } - - } - - object AttributeNames { - final case class List(names: scala.List[String]) extends AttributeNames - - case object All extends AttributeNames - - def fromJava(javaNames: String): AttributeNames = - javaNames.trim match { - case "*" => All - case list => List(list.split(',').toList) - } - - } - - final case class Attributes(attributeNames: AttributeNames, viewName: String = "base") { - def toJava: String = s"$viewName:${attributeNames.toJava}" - } - - object Attributes { - - def fromJava(javaAttributes: String): Option[Attributes] = - javaAttributes.split(':').toList match { - case names :: Nil => Some(Attributes(AttributeNames.fromJava(names))) - case view :: names :: Nil => Some(Attributes(AttributeNames.fromJava(names), view)) - case _ => None - } - - } - - def readAttributes( - path: Path, - attributes: Attributes, - linkOptions: LinkOption* - ): ZIO[Blocking, IOException, Map[String, AnyRef]] = - effectBlocking(JFiles.readAttributes(path.javaPath, attributes.toJava, linkOptions: _*)) - .map(_.asScala.toMap) - .refineToOrDie[IOException] - - def getPosixFilePermissions( - path: Path, - linkOptions: LinkOption* - ): ZIO[Blocking, IOException, Set[PosixFilePermission]] = - effectBlocking(JFiles.getPosixFilePermissions(path.javaPath, linkOptions: _*)) - .map(_.asScala.toSet) - .refineToOrDie[IOException] - - def setPosixFilePermissions(path: Path, permissions: Set[PosixFilePermission]): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.setPosixFilePermissions(path.javaPath, permissions.asJava)).unit - .refineToOrDie[IOException] - - def getOwner(path: Path, linkOptions: LinkOption*): ZIO[Blocking, IOException, UserPrincipal] = - effectBlocking(JFiles.getOwner(path.javaPath, linkOptions: _*)).refineToOrDie[IOException] - - def setOwner(path: Path, owner: UserPrincipal): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.setOwner(path.javaPath, owner)).unit.refineToOrDie[IOException] - - def isSymbolicLink(path: Path): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.isSymbolicLink(path.javaPath)).orDie - - def isDirectory(path: Path, linkOptions: LinkOption*): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.isDirectory(path.javaPath, linkOptions: _*)).orDie - - def isRegularFile(path: Path, linkOptions: LinkOption*): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.isRegularFile(path.javaPath, linkOptions: _*)).orDie - - def getLastModifiedTime(path: Path, linkOptions: LinkOption*): ZIO[Blocking, IOException, FileTime] = - effectBlocking(JFiles.getLastModifiedTime(path.javaPath, linkOptions: _*)).refineToOrDie[IOException] - - def setLastModifiedTime(path: Path, time: FileTime): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.setLastModifiedTime(path.javaPath, time)).unit.refineToOrDie[IOException] - - def size(path: Path): ZIO[Blocking, IOException, Long] = - effectBlocking(JFiles.size(path.javaPath)).refineToOrDie[IOException] - - def exists(path: Path, linkOptions: LinkOption*): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.exists(path.javaPath, linkOptions: _*)).orDie - - def notExists(path: Path, linkOptions: LinkOption*): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.notExists(path.javaPath, linkOptions: _*)).orDie - - def isReadable(path: Path): ZIO[Blocking, Nothing, Boolean] = effectBlocking(JFiles.isReadable(path.javaPath)).orDie - - def isWritable(path: Path): ZIO[Blocking, Nothing, Boolean] = effectBlocking(JFiles.isWritable(path.javaPath)).orDie - - def isExecutable(path: Path): ZIO[Blocking, Nothing, Boolean] = - effectBlocking(JFiles.isExecutable(path.javaPath)).orDie - - def readAllBytes(path: Path): ZIO[Blocking, IOException, Chunk[Byte]] = - effectBlocking(Chunk.fromArray(JFiles.readAllBytes(path.javaPath))).refineToOrDie[IOException] - - def readAllLines(path: Path, charset: Charset = Charset.Standard.utf8): ZIO[Blocking, IOException, List[String]] = - effectBlocking(JFiles.readAllLines(path.javaPath, charset.javaCharset).asScala.toList).refineToOrDie[IOException] - - def writeBytes(path: Path, bytes: Chunk[Byte], openOptions: OpenOption*): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.write(path.javaPath, bytes.toArray, openOptions: _*)).unit.refineToOrDie[IOException] - - def writeLines( - path: Path, - lines: Iterable[CharSequence], - charset: Charset = Charset.Standard.utf8, - openOptions: Set[OpenOption] = Set.empty - ): ZIO[Blocking, IOException, Unit] = - effectBlocking(JFiles.write(path.javaPath, lines.asJava, charset.javaCharset, openOptions.toSeq: _*)).unit - .refineToOrDie[IOException] - - def lines(path: Path, charset: Charset = Charset.Standard.utf8): ZStream[Blocking, IOException, String] = - ZStream - .fromJavaStreamManaged( - ZManaged.fromAutoCloseable(effectBlocking(JFiles.lines(path.javaPath, charset.javaCharset))) - ) - .refineToOrDie[IOException] - - def list(path: Path): ZStream[Blocking, IOException, Path] = - ZStream - .fromJavaStreamManaged( - ZManaged.fromAutoCloseable(effectBlocking(JFiles.list(path.javaPath))) - ) - .map(Path.fromJava) - .refineToOrDie[IOException] - - def walk( - path: Path, - maxDepth: Int = Int.MaxValue, - visitOptions: Set[FileVisitOption] = Set.empty - ): ZStream[Blocking, IOException, Path] = - ZStream - .fromJavaStreamManaged( - ZManaged.fromAutoCloseable(effectBlocking(JFiles.walk(path.javaPath, maxDepth, visitOptions.toSeq: _*))) - ) - .map(Path.fromJava) - .refineToOrDie[IOException] - - def find(path: Path, maxDepth: Int = Int.MaxValue, visitOptions: Set[FileVisitOption] = Set.empty)( - test: (Path, BasicFileAttributes) => Boolean - ): ZStream[Blocking, IOException, Path] = { - val matcher: BiPredicate[JPath, BasicFileAttributes] = (path, attr) => test(Path.fromJava(path), attr) - ZStream - .fromJavaStreamManaged( - ZManaged.fromAutoCloseable( - effectBlocking(JFiles.find(path.javaPath, maxDepth, matcher, visitOptions.toSeq: _*)) - ) - ) - .map(Path.fromJava) - .refineToOrDie[IOException] - } - - def copy( - in: ZStream[Blocking, IOException, Byte], - target: Path, - options: CopyOption* - ): ZIO[Blocking, IOException, Long] = - in.toInputStream - .use(inputStream => effectBlocking(JFiles.copy(inputStream, target.javaPath, options: _*))) - .refineToOrDie[IOException] - -} diff --git a/nio/src/main/scala/zio/nio/package.scala b/nio/src/main/scala/zio/nio/package.scala deleted file mode 100644 index 7d73f66a..00000000 --- a/nio/src/main/scala/zio/nio/package.scala +++ /dev/null @@ -1,70 +0,0 @@ -package zio - -import com.github.ghik.silencer.silent -import zio.ZManaged.ReleaseMap - -import java.io.EOFException - -/** - * ZIO-NIO, the API for using Java's NIO API in ZIO programs. - */ -package object nio { - - /** - * Handle -1 magic number returned by many Java read APIs when end of file is reached. - * - * Produces an `EOFException` failure if `value` < 0, otherwise succeeds with `value`. - */ - private[nio] def eofCheck(value: Int): IO[EOFException, Int] = - if (value < 0) IO.fail(new EOFException("Channel has reached the end of stream")) else IO.succeed(value) - - /** - * Handle -1 magic number returned by many Java read APIs when end of file is reached. - * - * Produces an `EOFException` failure if `value` < 0, otherwise succeeds with `value`. - */ - private[nio] def eofCheck(value: Long): IO[EOFException, Long] = - if (value < 0L) IO.fail(new EOFException("Channel has reached the end of stream")) else IO.succeed(value) - - implicit final class EffectOps[-R, +E, +A](private val effect: ZIO[R, E, A]) extends AnyVal { - - /** - * Explicitly represent end-of-stream in the error channel. - * - * This will catch an `EOFException` failure from the effect and translate it to a failure of `None`. Other - * exception types are wrapped in `Some`. - */ - @silent("parameter value ev in method .* is never used") - def eofCheck[E2 >: E](implicit ev: EOFException <:< E2): ZIO[R, Option[E2], A] = - effect.catchAll { - case _: EOFException => ZIO.fail(None) - case e => ZIO.fail(Some(e)) - } - - } - - implicit final private[nio] class IOCloseableManagement[-R, +E, +A <: IOCloseable]( - private val acquire: ZIO[R, E, A] - ) extends AnyVal { - - def toNioManaged: ZManaged[R, E, A] = ZManaged.makeInterruptible(acquire)(_.close.ignore) - - } - - implicit final class ManagedOps[-R, +E, +A](private val managed: ZManaged[R, E, A]) extends AnyVal { - - /** - * Use this managed resource in an effect running in a forked fiber. The resource will be released on the forked - * fiber after the effect exits, whether it succeeds, fails or is interrupted. - * - * @param f - * The effect to run in a forked fiber. The resource is only valid within this effect. - */ - def useForked[R2 <: R, E2 >: E, B](f: A => ZIO[R2, E2, B]): ZIO[R2, E, Fiber[E2, B]] = - ReleaseMap.make.flatMap { releaseMap => - managed.zio.provideSome[R]((_, releaseMap)).flatMap { case (finalizer, a) => f(a).onExit(finalizer).fork } - } - - } - -} diff --git a/nio/src/test/scala/zio/nio/BaseSpec.scala b/nio/src/test/scala/zio/nio/BaseSpec.scala deleted file mode 100644 index 491f610a..00000000 --- a/nio/src/test/scala/zio/nio/BaseSpec.scala +++ /dev/null @@ -1,9 +0,0 @@ -package zio.nio - -import zio.duration._ -import zio.test.environment.Live -import zio.test.{DefaultRunnableSpec, TestAspect, TestAspectAtLeastR} - -trait BaseSpec extends DefaultRunnableSpec { - override def aspects: List[TestAspectAtLeastR[Live]] = List(TestAspect.timeout(60.seconds)) -} diff --git a/nio/src/test/scala/zio/nio/channels/ChannelSpec.scala b/nio/src/test/scala/zio/nio/channels/ChannelSpec.scala deleted file mode 100644 index 2fe1fb5c..00000000 --- a/nio/src/test/scala/zio/nio/channels/ChannelSpec.scala +++ /dev/null @@ -1,258 +0,0 @@ -package zio.nio.channels - -import zio.blocking.Blocking -import zio.clock.Clock -import zio.duration._ -import zio.nio.{BaseSpec, Buffer, EffectOps, InetSocketAddress, SocketAddress} -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ -import zio.test.environment.{Live, TestClock, TestConsole, TestRandom, TestSystem, live} -import zio.{IO, _} - -import java.io.{EOFException, FileNotFoundException, IOException} -import java.nio.channels -import java.{nio => jnio} - -object ChannelSpec extends BaseSpec { - - override def spec: Spec[Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = - suite("Channel")( - testM("localAddress") { - SocketChannel.open.use { con => - for { - _ <- con.bindAuto - localAddress <- con.localAddress - } yield assert(localAddress)(isSome(isSubtype[InetSocketAddress](anything))) - } - }, - suite("AsynchronousSocketChannel")( - testM("read/write") { - def echoServer(started: Promise[Nothing, SocketAddress]): IO[Exception, Unit] = - for { - sink <- Buffer.byte(3) - _ <- AsynchronousServerSocketChannel.open.use { server => - for { - _ <- server.bindAuto() - addr <- server.localAddress.flatMap(opt => IO.effect(opt.get).orDie) - _ <- started.succeed(addr) - _ <- server.accept.use { worker => - worker.read(sink) *> - sink.flip *> - worker.write(sink) - } - } yield () - }.fork - } yield () - - def echoClient(address: SocketAddress): IO[Exception, Boolean] = - for { - src <- Buffer.byte(3) - result <- AsynchronousSocketChannel.open.use { client => - for { - _ <- client.connect(address) - sent <- src.array - _ = sent.update(0, 1) - _ <- client.write(src) - _ <- src.flip - _ <- client.read(src) - received <- src.array - } yield sent.sameElements(received) - } - } yield result - - for { - serverStarted <- Promise.make[Nothing, SocketAddress] - _ <- echoServer(serverStarted) - address <- serverStarted.await - same <- echoClient(address) - } yield assert(same)(isTrue) - }, - testM("read should fail when connection close") { - def server(started: Promise[Nothing, SocketAddress]): IO[Exception, Fiber[Exception, Boolean]] = - for { - result <- AsynchronousServerSocketChannel.open.use { server => - for { - _ <- server.bindAuto() - addr <- server.localAddress.flatMap(opt => IO.effect(opt.get).orDie) - _ <- started.succeed(addr) - result <- server.accept - .use(worker => worker.readChunk(3) *> worker.readChunk(3) *> ZIO.succeed(false)) - .catchSome { case _: java.io.EOFException => - ZIO.succeed(true) - } - } yield result - }.fork - } yield result - - def client(address: SocketAddress): IO[Exception, Unit] = - for { - _ <- AsynchronousSocketChannel.open.use { client => - for { - _ <- client.connect(address) - _ = client.writeChunk(Chunk.fromArray(Array[Byte](1, 1, 1))) - } yield () - } - } yield () - - for { - serverStarted <- Promise.make[Nothing, SocketAddress] - serverFiber <- server(serverStarted) - addr <- serverStarted.await - _ <- client(addr) - same <- serverFiber.join - } yield assert(same)(isTrue) - }, - testM("close channel unbind port") { - def client(address: SocketAddress): IO[Exception, Unit] = - AsynchronousSocketChannel.open.use { - _.connect(address) - } - - def server( - started: Promise[Nothing, SocketAddress] - ): Managed[IOException, Fiber[Exception, Unit]] = - for { - server <- AsynchronousServerSocketChannel.open - _ <- server.bindAuto().toManaged_ - addr <- server.localAddress.someOrElseM(IO.die(new NoSuchElementException)).toManaged_ - _ <- started.succeed(addr).toManaged_ - worker <- server.accept.unit.fork - } yield worker - - for { - serverStarted1 <- Promise.make[Nothing, SocketAddress] - _ <- server(serverStarted1).use { s1 => - serverStarted1.await.flatMap(client).zipRight(s1.join) - } - serverStarted2 <- Promise.make[Nothing, SocketAddress] - _ <- server(serverStarted2).use { s2 => - serverStarted2.await.flatMap(client).zipRight(s2.join) - } - } yield assertCompletes - }, - testM("read can be interrupted") { - live { - AsynchronousServerSocketChannel.open - .tapM(_.bindAuto()) - .use { serverChannel => - for { - serverAddress <- serverChannel.localAddress.someOrElseM(ZIO.dieMessage("Local address must be bound")) - promise <- Promise.make[Nothing, Unit] - fiber <- AsynchronousSocketChannel.open - .tapM(_.connect(serverAddress)) - .use(channel => promise.succeed(()) *> channel.readChunk(1)) - .fork - _ <- promise.await - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - - } - } - }, - testM("accept can be interrupted") { - live { - AsynchronousServerSocketChannel.open.tapM(_.bindAuto()).use { serverChannel => - for { - fiber <- serverChannel.accept.useNow.fork - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - } - } - } - ), - suite("explicit end-of-stream")( - testM("converts EOFException to None") { - assertM(IO.fail(new EOFException).eofCheck.run)(fails(isNone)) - }, - testM("converts non EOFException to Some") { - val e: IOException = new FileNotFoundException() - assertM(IO.fail(e).eofCheck.run)(fails(isSome(equalTo(e)))) - }, - testM("passes through success") { - assertM(IO.succeed(42).eofCheck.run)(succeeds(equalTo(42))) - } - ), - suite("blocking operations")( - testM("read can be interrupted") { - live { - for { - promise <- Promise.make[Nothing, Unit] - fiber <- Pipe.open.toManaged_ - .flatMap(_.source) - .useNioBlockingOps(ops => promise.succeed(()) *> ops.readChunk(1)) - .fork - _ <- promise.await - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - } - }, - testM("write can be interrupted") { - val hangingOps: GatheringByteOps = new GatheringByteOps { - override protected[channels] val channel = new jnio.channels.GatheringByteChannel { - - @volatile private var _closed = false - - private def hang(): Nothing = { - while (!_closed) - Thread.sleep(10L) - throw new jnio.channels.AsynchronousCloseException() - } - - override def write(srcs: Array[jnio.ByteBuffer], offset: Int, length: Int): Long = hang() - - override def write(srcs: Array[jnio.ByteBuffer]): Long = hang() - - override def write(src: jnio.ByteBuffer): Int = hang() - - override def isOpen: Boolean = !_closed - - override def close(): Unit = _closed = true - } - } - val hangingChannel = new BlockingChannel { - override type BlockingOps = GatheringByteOps - - override def useBlocking[R, E >: IOException, A]( - f: GatheringByteOps => ZIO[R, E, A] - ): ZIO[R with Blocking, E, A] = nioBlocking(f(hangingOps)) - - override protected val channel: channels.Channel = hangingOps.channel - } - - live { - for { - promise <- Promise.make[Nothing, Unit] - fiber <- - hangingChannel.useBlocking(ops => promise.succeed(()) *> ops.writeChunk(Chunk.single(42.toByte))).fork - _ <- promise.await - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - } - }, - testM("accept can be interrupted") { - live { - ServerSocketChannel.open.tapM(_.bindAuto()).use { serverChannel => - for { - promise <- Promise.make[Nothing, Unit] - fiber <- serverChannel.useBlocking(ops => promise.succeed(()) *> ops.accept.useNow).fork - _ <- promise.await - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - } - } - } - ) - ) -} diff --git a/nio/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala b/nio/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala deleted file mode 100644 index 35f18aed..00000000 --- a/nio/src/test/scala/zio/nio/channels/DatagramChannelSpec.scala +++ /dev/null @@ -1,95 +0,0 @@ -package zio.nio.channels - -import zio._ -import zio.blocking.Blocking -import zio.clock.Clock -import zio.nio._ -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ -import zio.test.environment.{Live, TestClock, TestConsole, TestRandom, TestSystem} - -import java.io.IOException - -object DatagramChannelSpec extends BaseSpec { - - override def spec: Spec[Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = - suite("DatagramChannelSpec")( - testM("read/write") { - def echoServer(started: Promise[Nothing, SocketAddress]): ZIO[Blocking, Nothing, Unit] = - for { - sink <- Buffer.byte(3) - _ <- DatagramChannel.open.useNioBlocking { (server, ops) => - for { - _ <- server.bindAuto - addr <- server.localAddress.someOrElseM(ZIO.dieMessage("Must have local address")) - _ <- started.succeed(addr) - addr <- ops.receive(sink) - _ <- sink.flip - _ <- ops.send(sink, addr) - } yield () - }.fork - } yield () - - def echoClient(address: SocketAddress): ZIO[Blocking, IOException, Boolean] = - for { - src <- Buffer.byte(3) - result <- DatagramChannel.open.useNioBlockingOps { client => - for { - _ <- client.connect(address) - sent <- src.array - _ = sent.update(0, 1) - _ <- client.send(src, address) - _ <- src.flip - _ <- client.read(src) - received <- src.array - } yield sent.sameElements(received) - } - } yield result - - for { - serverStarted <- Promise.make[Nothing, SocketAddress] - _ <- echoServer(serverStarted) - addr <- serverStarted.await - same <- echoClient(addr) - } yield assert(same)(isTrue) - }, - testM("close channel unbind port") { - def client(address: SocketAddress): ZIO[Blocking, IOException, Unit] = - DatagramChannel.open.useNioBlockingOps(_.connect(address).unit) - - def server( - address: Option[SocketAddress], - started: Promise[Nothing, SocketAddress] - ): ZIO[Blocking, Nothing, Fiber[IOException, Unit]] = - for { - worker <- DatagramChannel.open.useNioBlocking { (server, _) => - for { - _ <- server.bind(address) - addr <- server.localAddress.someOrElseM(ZIO.dieMessage("Local address must be bound")) - _ <- started.succeed(addr) - } yield () - }.fork - } yield worker - - for { - serverStarted <- Promise.make[Nothing, SocketAddress] - s1 <- server(None, serverStarted) - addr <- serverStarted.await - _ <- client(addr) - _ <- s1.join - serverStarted2 <- Promise.make[Nothing, SocketAddress] - s2 <- server(Some(addr), serverStarted2) - _ <- serverStarted2.await - _ <- client(addr) - _ <- s2.join - } yield assertCompletes - } - ) -} diff --git a/nio/src/test/scala/zio/nio/channels/SelectorSpec.scala b/nio/src/test/scala/zio/nio/channels/SelectorSpec.scala deleted file mode 100644 index c4f5d2d0..00000000 --- a/nio/src/test/scala/zio/nio/channels/SelectorSpec.scala +++ /dev/null @@ -1,131 +0,0 @@ -package zio.nio.channels - -import zio._ -import zio.blocking.Blocking -import zio.clock.Clock -import zio.duration.durationInt -import zio.nio.channels.SelectionKey.Operation -import zio.nio.{BaseSpec, Buffer, ByteBuffer, SocketAddress} -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ -import zio.test.environment.{Live, TestClock, TestConsole, TestRandom, TestSystem, live} - -import java.io.IOException -import java.nio.channels.CancelledKeyException - -object SelectorSpec extends BaseSpec { - - override def spec: Spec[Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = - suite("SelectorSpec")( - testM("read/write") { - for { - started <- Promise.make[Nothing, SocketAddress] - serverFiber <- server(started).useNow.fork - addr <- started.await - clientFiber <- client(addr).fork - _ <- serverFiber.join - message <- clientFiber.join - } yield assert(message)(equalTo("Hello world")) - }, - testM("select is interruptible") { - live { - Selector.open.use { selector => - for { - fiber <- selector.select.fork - _ <- ZIO.sleep(500.milliseconds) - exit <- fiber.interrupt - } yield assert(exit)(isInterrupted) - } - } - } - ) - - def byteArrayToString(array: Array[Byte]): String = array.takeWhile(_ != 10).map(_.toChar).mkString.trim - - def safeStatusCheck(statusCheck: IO[CancelledKeyException, Boolean]): IO[Nothing, Boolean] = - statusCheck.fold(_ => false, identity) - - def server(started: Promise[Nothing, SocketAddress]): ZManaged[Clock with Blocking, Exception, Unit] = { - def serverLoop( - scope: Managed.Scope, - selector: Selector, - buffer: ByteBuffer - ): ZIO[Blocking, Exception, Unit] = - for { - _ <- selector.select - _ <- selector.foreachSelectedKey { key => - key.matchChannel { readyOps => - { - case channel: ServerSocketChannel if readyOps(Operation.Accept) => - for { - scopeResult <- scope(channel.useNonBlockingManaged(_.accept)) - (_, maybeClient) = scopeResult - _ <- IO.whenCase(maybeClient) { case Some(client) => - client.configureBlocking(false) *> client.register(selector, Set(Operation.Read)) - } - } yield () - case channel: SocketChannel if readyOps(Operation.Read) => - channel.useNonBlocking { client => - for { - _ <- client.read(buffer) - _ <- buffer.flip - _ <- client.write(buffer) - _ <- buffer.clear - _ <- channel.close - } yield () - } - } - } - .as(true) - } - _ <- selector.selectedKeys.filterOrDieMessage(_.isEmpty)("Selected key set should be empty") - } yield () - - for { - scope <- Managed.scope - selector <- Selector.open - channel <- ServerSocketChannel.open - _ <- Managed.fromEffect { - for { - _ <- channel.bindAuto() - _ <- channel.configureBlocking(false) - _ <- channel.register(selector, Set(Operation.Accept)) - buffer <- Buffer.byte(256) - addr <- channel.localAddress - _ <- started.succeed(addr) - - /* - * we need to run the server loop twice: - * 1. to accept the client request - * 2. to read from the client channel - */ - _ <- serverLoop(scope, selector, buffer).repeat(Schedule.once) - } yield () - } - } yield () - } - - def client(address: SocketAddress): ZIO[Blocking, IOException, String] = { - val bytes = Chunk.fromArray("Hello world".getBytes) - for { - buffer <- Buffer.byte(bytes) - text <- SocketChannel.open(address).useNioBlockingOps { client => - for { - _ <- client.write(buffer) - _ <- buffer.clear - _ <- client.read(buffer) - array <- buffer.array - text = byteArrayToString(array) - _ <- buffer.clear - } yield text - } - } yield text - } -} diff --git a/nio/src/test/scala/zio/nio/file/FilesSpec.scala b/nio/src/test/scala/zio/nio/file/FilesSpec.scala deleted file mode 100644 index c01d9a73..00000000 --- a/nio/src/test/scala/zio/nio/file/FilesSpec.scala +++ /dev/null @@ -1,94 +0,0 @@ -package zio.nio.file - -import zio.blocking.Blocking -import zio.clock.Clock -import zio.nio.BaseSpec -import zio.random.Random -import zio.test.Assertion._ -import zio.test._ -import zio.test.environment._ -import zio.{Chunk, Has, Ref} - -object FilesSpec extends BaseSpec { - - override def spec: Spec[Has[Annotations.Service] with Has[Live.Service] with Has[Sized.Service] with Has[ - TestClock.Service - ] with Has[TestConfig.Service] with Has[TestConsole.Service] with Has[TestRandom.Service] with Has[ - TestSystem.Service - ] with Has[Clock.Service] with Has[zio.console.Console.Service] with Has[zio.system.System.Service] with Has[ - Random.Service - ] with Has[Blocking.Service], TestFailure[Any], TestSuccess] = - suite("FilesSpec")( - testM("createTempFileInManaged cleans up temp file") { - val sampleFileContent = Chunk.fromArray("createTempFileInManaged works!".getBytes) - for { - pathRef <- Ref.make[Option[Path]](None) - readBytes <- Files - .createTempFileInManaged(dir = Path(".")) - .use { tmpFile => - pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) - } - Some(tmpFilePath) <- pathRef.get - tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) - } yield assert(readBytes)(equalTo(sampleFileContent)) && - assert(tmpFileExistsAfterUsage)(isFalse) - }, - testM("createTempFileManaged cleans up temp file") { - val sampleFileContent = Chunk.fromArray("createTempFileManaged works!".getBytes) - for { - pathRef <- Ref.make[Option[Path]](None) - readBytes <- Files - .createTempFileManaged() - .use { tmpFile => - pathRef.set(Some(tmpFile)) *> writeAndThenRead(tmpFile)(sampleFileContent) - } - Some(tmpFilePath) <- pathRef.get - tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) - } yield assert(readBytes)(equalTo(sampleFileContent)) && - assert(tmpFileExistsAfterUsage)(isFalse) - }, - testM("createTempDirectoryManaged cleans up temp dir") { - val sampleFileContent = Chunk.fromArray("createTempDirectoryManaged works!".getBytes) - for { - pathRef <- Ref.make[Option[Path]](None) - readBytes <- Files - .createTempDirectoryManaged( - prefix = None, - fileAttributes = Nil - ) - .use { tmpDir => - val sampleFile = tmpDir / "createTempDirectoryManaged" - pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) - } - Some(tmpFilePath) <- pathRef.get - tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) - } yield assert(readBytes)(equalTo(sampleFileContent)) && - assert(tmpFileExistsAfterUsage)(isFalse) - }, - testM("createTempDirectoryManaged (dir) cleans up temp dir") { - val sampleFileContent = Chunk.fromArray("createTempDirectoryManaged(dir) works!".getBytes) - for { - pathRef <- Ref.make[Option[Path]](None) - readBytes <- Files - .createTempDirectoryManaged( - dir = Path("."), - prefix = None, - fileAttributes = Nil - ) - .use { tmpDir => - val sampleFile = tmpDir / "createTempDirectoryManaged2" - pathRef.set(Some(tmpDir)) *> createAndWriteAndThenRead(sampleFile)(sampleFileContent) - } - Some(tmpFilePath) <- pathRef.get - tmpFileExistsAfterUsage <- Files.exists(tmpFilePath) - } yield assert(readBytes)(equalTo(sampleFileContent)) && - assert(tmpFileExistsAfterUsage)(isFalse) - } - ) - - private def createAndWriteAndThenRead(file: Path)(bytes: Chunk[Byte]) = - Files.createFile(file) *> writeAndThenRead(file)(bytes) - - private def writeAndThenRead(file: Path)(bytes: Chunk[Byte]) = - Files.writeBytes(file, bytes) *> Files.readAllBytes(file) -} diff --git a/nio/src/test/scala/zio/nio/file/WathServiceSpec.scala b/nio/src/test/scala/zio/nio/file/WathServiceSpec.scala deleted file mode 100644 index 79e20c63..00000000 --- a/nio/src/test/scala/zio/nio/file/WathServiceSpec.scala +++ /dev/null @@ -1,24 +0,0 @@ -package zio.nio.file - -import zio.blocking.Blocking -import zio.nio.BaseSpec -import zio.test.Assertion._ -import zio.test._ - -import java.io.IOException -import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE - -object WathServiceSpec extends BaseSpec { - - override def spec: Spec[Blocking, TestFailure[IOException], TestSuccess] = - suite("WatchServiceSpec")( - testM("Watch Service register")( - FileSystem.default.newWatchService.use { watchService => - for { - watchKey <- Path("nio/src/test/resources").register(watchService, ENTRY_CREATE) - watchable = watchKey.watchable - } yield assert(watchable)(equalTo(Path("nio/src/test/resources"))) - } - ) - ) -} diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index e722a726..10f7570c 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -22,13 +22,11 @@ object BuildHelper { val vs = v.split('.'); val init = vs.take(vs(0) match { case "2" => 2; case _ => 1 }); (init.mkString("."), v) }.toMap } - val Scala211: String = versions("2.11") + val Scala212: String = versions("2.12") val Scala213: String = versions("2.13") val Scala3: String = versions("3") - val SilencerVersion = "1.7.8" - private val stdOptions = Seq( "-deprecation", "-encoding", @@ -48,7 +46,7 @@ object BuildHelper { "-language:existentials", "-explaintypes", "-Yrangepos", - "-Xlint:_,-missing-interpolator,-type-parameter-shadow", + "-Xlint:_,-missing-interpolator,-type-parameter-shadow,-infer-any", "-Ywarn-numeric-widen", "-Ywarn-value-discard" ) @@ -67,7 +65,7 @@ object BuildHelper { buildInfoPackage := packageName ) - val dottySettings = Seq( + val scala3Settings = Seq( crossScalaVersions += Scala3, scalacOptions --= { if (scalaVersion.value == Scala3) @@ -93,6 +91,25 @@ object BuildHelper { } ) + lazy val crossProjectSettings = Seq( + Compile / unmanagedSourceDirectories ++= { + crossPlatformSources( + scalaVersion.value, + crossProjectPlatform.value.identifier, + "main", + baseDirectory.value + ) + }, + Test / unmanagedSourceDirectories ++= { + crossPlatformSources( + scalaVersion.value, + crossProjectPlatform.value.identifier, + "test", + baseDirectory.value + ) + } + ) + def makeReplSettings(initialCommandsStr: String) = Seq( // In the repl most warnings are useless or worse. @@ -132,7 +149,6 @@ object BuildHelper { "-Ypartial-unification", "-Yno-adapted-args", "-Ywarn-inaccessible", - "-Ywarn-infer-any", "-Ywarn-nullary-override", "-Ywarn-nullary-unit", "-Ywarn-unused:params,-implicits", @@ -146,7 +162,6 @@ object BuildHelper { "-Ypartial-unification", "-Yno-adapted-args", "-Ywarn-inaccessible", - "-Ywarn-infer-any", "-Ywarn-nullary-override", "-Ywarn-nullary-unit", "-Xexperimental", @@ -186,33 +201,11 @@ object BuildHelper { def stdSettings(prjName: String) = Seq( name := s"$prjName", - crossScalaVersions := Seq(Scala211, Scala212, Scala213), + crossScalaVersions := Seq(Scala212, Scala213), ThisBuild / scalaVersion := Scala213, scalacOptions := stdOptions ++ extraOptions(scalaVersion.value, optimize = !isSnapshot.value), - libraryDependencies ++= { - if (scalaVersion.value == Scala3) - Seq( - "com.github.ghik" % s"silencer-lib_$Scala213" % SilencerVersion % Provided - ) - else - Seq( - ("com.github.ghik" % "silencer-lib" % SilencerVersion % Provided).cross(CrossVersion.full), - compilerPlugin(("com.github.ghik" % "silencer-plugin" % SilencerVersion).cross(CrossVersion.full)), - compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)) - ) - }, - semanticdbEnabled := scalaVersion.value != Scala3, // enable SemanticDB - semanticdbOptions += "-P:semanticdb:synthetics:on", - semanticdbVersion := scalafixSemanticdb.revision, // use Scalafix compatible version - ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value), - ThisBuild / scalafixDependencies ++= List( - "com.github.liancheng" %% "organize-imports" % "0.6.0", - "com.github.vovapolu" %% "scaluzzi" % "0.1.20" - ), Test / parallelExecution := true, - incOptions ~= (_.withLogRecompileOnMacro(false)), - autoAPIMappings := true, - unusedCompileDependenciesFilter -= moduleFilter("org.scala-js", "scalajs-library") + incOptions ~= (_.withLogRecompileOnMacro(false)) ) def welcomeMessage = diff --git a/project/build.properties b/project/build.properties index 8378cad5..f344c148 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.5.7 +sbt.version = 1.8.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index c2551429..43dbda71 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,20 +1,20 @@ -addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.13") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.6") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11") addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.16") -addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.6.5") -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.4") +addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.1") +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.9.0") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.12") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.24") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6") addSbtPlugin("pl.project13.scala" % "sbt-jcstress" % "0.2.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") -libraryDependencies += "org.snakeyaml" % "snakeyaml-engine" % "2.3" +libraryDependencies += "org.snakeyaml" % "snakeyaml-engine" % "2.6"