2018-10-29 17:19:37 +08:00
|
|
|
/*
|
2019-01-02 18:55:26 +08:00
|
|
|
* Copyright (C) 2015-2019 Lightbend Inc. <https://www.lightbend.com>
|
2015-04-16 02:24:01 +02:00
|
|
|
*/
|
2018-03-13 23:45:55 +09:00
|
|
|
|
2015-05-29 16:43:02 +02:00
|
|
|
package akka.stream.impl.io
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2016-04-25 19:25:26 +10:00
|
|
|
import java.io.InputStream
|
2016-09-21 23:42:27 +02:00
|
|
|
import java.nio.ByteBuffer
|
|
|
|
|
import java.nio.channels.{ CompletionHandler, FileChannel }
|
2018-02-08 09:18:35 +01:00
|
|
|
import java.nio.file.{ Files, NoSuchFileException, Path, StandardOpenOption }
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
import akka.Done
|
2017-03-16 21:04:07 +02:00
|
|
|
import akka.annotation.InternalApi
|
2018-02-22 13:42:59 +01:00
|
|
|
import akka.stream.ActorAttributes.Dispatcher
|
2016-09-21 23:42:27 +02:00
|
|
|
import akka.stream.Attributes.InputBuffer
|
2015-04-16 02:24:01 +02:00
|
|
|
import akka.stream.impl.{ ErrorPublisher, SourceModule }
|
2016-09-21 23:42:27 +02:00
|
|
|
import akka.stream.stage._
|
|
|
|
|
import akka.stream.{ IOResult, _ }
|
2015-11-14 22:42:22 +01:00
|
|
|
import akka.util.ByteString
|
2016-09-21 23:42:27 +02:00
|
|
|
import org.reactivestreams.Publisher
|
2017-03-16 21:04:07 +02:00
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
import scala.annotation.tailrec
|
2015-11-14 22:42:22 +01:00
|
|
|
import scala.concurrent.{ Future, Promise }
|
2016-09-21 23:42:27 +02:00
|
|
|
import scala.util.control.NonFatal
|
|
|
|
|
import scala.util.{ Failure, Success, Try }
|
2015-04-16 02:24:01 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
private[akka] object FileSource {
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
val completionHandler = new CompletionHandler[Integer, Try[Int] => Unit] {
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
override def completed(result: Integer, attachment: Try[Int] => Unit): Unit = {
|
2016-09-21 23:42:27 +02:00
|
|
|
attachment(Success(result))
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
override def failed(ex: Throwable, attachment: Try[Int] => Unit): Unit = {
|
2016-09-21 23:42:27 +02:00
|
|
|
attachment(Failure(ex))
|
|
|
|
|
}
|
2015-04-16 02:24:01 +02:00
|
|
|
}
|
2016-09-21 23:42:27 +02:00
|
|
|
}
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
* Creates simple asynchronous Source backed by the given file.
|
|
|
|
|
*/
|
|
|
|
|
private[akka] final class FileSource(path: Path, chunkSize: Int, startPosition: Long)
|
|
|
|
|
extends GraphStageWithMaterializedValue[SourceShape[ByteString], Future[IOResult]] {
|
|
|
|
|
require(chunkSize > 0, "chunkSize must be greater than 0")
|
|
|
|
|
val out = Outlet[ByteString]("FileSource.out")
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
override val shape = SourceShape(out)
|
|
|
|
|
|
|
|
|
|
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, Future[IOResult]) = {
|
|
|
|
|
val ioResultPromise = Promise[IOResult]()
|
2016-03-11 17:08:30 +01:00
|
|
|
|
2016-09-21 23:42:27 +02:00
|
|
|
val logic = new GraphStageLogic(shape) with OutHandler {
|
2019-02-09 15:25:39 +01:00
|
|
|
handler =>
|
2016-09-21 23:42:27 +02:00
|
|
|
val buffer = ByteBuffer.allocate(chunkSize)
|
2018-08-20 16:43:46 +02:00
|
|
|
val maxReadAhead = inheritedAttributes.get[InputBuffer](InputBuffer(16, 16)).max
|
2016-09-21 23:42:27 +02:00
|
|
|
var channel: FileChannel = _
|
|
|
|
|
var position = startPosition
|
2019-02-09 15:25:39 +01:00
|
|
|
var chunkCallback: Try[Int] => Unit = _
|
2016-09-21 23:42:27 +02:00
|
|
|
var eofEncountered = false
|
|
|
|
|
var availableChunks: Vector[ByteString] = Vector.empty[ByteString]
|
|
|
|
|
|
|
|
|
|
setHandler(out, this)
|
|
|
|
|
|
|
|
|
|
override def preStart(): Unit = {
|
|
|
|
|
try {
|
|
|
|
|
// this is a bit weird but required to keep existing semantics
|
2018-02-08 09:18:35 +01:00
|
|
|
if (!Files.exists(path)) throw new NoSuchFileException(path.toString)
|
|
|
|
|
|
2018-07-12 07:31:16 +01:00
|
|
|
require(!Files.isDirectory(path), s"Path '$path' is a directory")
|
2016-09-21 23:42:27 +02:00
|
|
|
require(Files.isReadable(path), s"Missing read permission for '$path'")
|
|
|
|
|
|
|
|
|
|
channel = FileChannel.open(path, StandardOpenOption.READ)
|
|
|
|
|
channel.position(position)
|
|
|
|
|
} catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case ex: Exception =>
|
2016-09-21 23:42:27 +02:00
|
|
|
ioResultPromise.trySuccess(IOResult(position, Failure(ex)))
|
|
|
|
|
throw ex
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def onPull(): Unit = {
|
|
|
|
|
if (availableChunks.size < maxReadAhead && !eofEncountered)
|
|
|
|
|
availableChunks = readAhead(maxReadAhead, availableChunks)
|
|
|
|
|
//if already read something and try
|
|
|
|
|
if (availableChunks.nonEmpty) {
|
|
|
|
|
emitMultiple(out, availableChunks.iterator,
|
2019-02-09 15:25:39 +01:00
|
|
|
() => if (eofEncountered) success() else setHandler(out, handler)
|
2016-09-21 23:42:27 +02:00
|
|
|
)
|
|
|
|
|
availableChunks = Vector.empty[ByteString]
|
|
|
|
|
} else if (eofEncountered) success()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def success(): Unit = {
|
|
|
|
|
completeStage()
|
|
|
|
|
ioResultPromise.trySuccess(IOResult(position, Success(Done)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** BLOCKING I/O READ */
|
|
|
|
|
@tailrec def readAhead(maxChunks: Int, chunks: Vector[ByteString]): Vector[ByteString] =
|
|
|
|
|
if (chunks.size < maxChunks && !eofEncountered) {
|
|
|
|
|
val readBytes = try channel.read(buffer, position) catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case NonFatal(ex) =>
|
2016-09-21 23:42:27 +02:00
|
|
|
failStage(ex)
|
|
|
|
|
ioResultPromise.trySuccess(IOResult(position, Failure(ex)))
|
|
|
|
|
throw ex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (readBytes > 0) {
|
|
|
|
|
buffer.flip()
|
|
|
|
|
position += readBytes
|
|
|
|
|
val newChunks = chunks :+ ByteString.fromByteBuffer(buffer)
|
|
|
|
|
buffer.clear()
|
|
|
|
|
|
|
|
|
|
if (readBytes < chunkSize) {
|
|
|
|
|
eofEncountered = true
|
|
|
|
|
newChunks
|
|
|
|
|
} else readAhead(maxChunks, newChunks)
|
|
|
|
|
} else {
|
|
|
|
|
eofEncountered = true
|
|
|
|
|
chunks
|
|
|
|
|
}
|
|
|
|
|
} else chunks
|
|
|
|
|
|
|
|
|
|
override def onDownstreamFinish(): Unit = success()
|
|
|
|
|
|
|
|
|
|
override def postStop(): Unit = {
|
|
|
|
|
ioResultPromise.trySuccess(IOResult(position, Success(Done)))
|
|
|
|
|
if ((channel ne null) && channel.isOpen) channel.close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(logic, ioResultPromise.future)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def toString = s"FileSource($path, $chunkSize)"
|
2015-04-16 02:24:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
* Source backed by the given input stream.
|
|
|
|
|
*/
|
2019-02-09 15:25:39 +01:00
|
|
|
@InternalApi private[akka] final class InputStreamSource(createInputStream: () => InputStream, chunkSize: Int, val attributes: Attributes, shape: SourceShape[ByteString])
|
2016-01-21 18:06:42 +02:00
|
|
|
extends SourceModule[ByteString, Future[IOResult]](shape) {
|
2015-04-16 02:24:01 +02:00
|
|
|
override def create(context: MaterializationContext) = {
|
2016-05-03 18:58:26 -07:00
|
|
|
val materializer = ActorMaterializerHelper.downcast(context.materializer)
|
2016-01-21 18:06:42 +02:00
|
|
|
val ioResultPromise = Promise[IOResult]()
|
2015-04-16 02:24:01 +02:00
|
|
|
|
|
|
|
|
val pub = try {
|
|
|
|
|
val is = createInputStream() // can throw, i.e. FileNotFound
|
|
|
|
|
|
2018-03-07 15:12:34 +01:00
|
|
|
val props = InputStreamPublisher
|
|
|
|
|
.props(is, ioResultPromise, chunkSize)
|
|
|
|
|
.withDispatcher(Dispatcher.resolve(context))
|
2015-04-16 02:24:01 +02:00
|
|
|
|
2015-12-11 14:45:24 +01:00
|
|
|
val ref = materializer.actorOf(context, props)
|
2015-04-16 02:24:01 +02:00
|
|
|
akka.stream.actor.ActorPublisher[ByteString](ref)
|
|
|
|
|
} catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case ex: Exception =>
|
2016-01-21 18:06:42 +02:00
|
|
|
ioResultPromise.failure(ex)
|
2015-04-16 02:24:01 +02:00
|
|
|
ErrorPublisher(ex, attributes.nameOrDefault("inputStreamSource")).asInstanceOf[Publisher[ByteString]]
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-21 18:06:42 +02:00
|
|
|
(pub, ioResultPromise.future)
|
2015-04-16 02:24:01 +02:00
|
|
|
}
|
|
|
|
|
|
2016-01-21 18:06:42 +02:00
|
|
|
override protected def newInstance(shape: SourceShape[ByteString]): SourceModule[ByteString, Future[IOResult]] =
|
2015-04-16 02:24:01 +02:00
|
|
|
new InputStreamSource(createInputStream, chunkSize, attributes, shape)
|
|
|
|
|
|
2016-07-27 13:29:23 +02:00
|
|
|
override def withAttributes(attr: Attributes): SourceModule[ByteString, Future[IOResult]] =
|
2015-04-16 02:24:01 +02:00
|
|
|
new InputStreamSource(createInputStream, chunkSize, attr, amendShape(attr))
|
2017-10-06 10:30:28 +02:00
|
|
|
}
|