简体   繁体   English

如何从多个文件写入组装Akka Streams接收器?

[英]How to assemble an Akka Streams sink from multiple file writes?

I'm trying to integrate an akka streams based flow in to my Play 2.5 app. 我正在尝试将基于akka流的流程集成到我的Play 2.5应用程序中。 The idea is that you can stream in a photo, then have it written to disk as the raw file, a thumbnailed version and a watermarked version. 我们的想法是,您可以在照片中流式传输,然后将其作为原始文件,缩略图版本和水印版本写入磁盘。

I managed to get this working using a graph something like this: 我设法使用这样的图形来使用它:

val byteAccumulator = Flow[ByteString].fold(new ByteStringBuilder())((builder, b) => {builder ++= b.toArray})
                                    .map(_.result().toArray)

def toByteArray = Flow[ByteString].map(b => b.toArray)

val graph = Flow.fromGraph(GraphDSL.create() {implicit builder =>
  import GraphDSL.Implicits._
  val streamFan = builder.add(Broadcast[ByteString](3))
  val byteArrayFan = builder.add(Broadcast[Array[Byte]](2))
  val output = builder.add(Flow[ByteString].map(x => Success(Done)))

  val rawFileSink = FileIO.toFile(file)
  val thumbnailFileSink = FileIO.toFile(getFile(path, Thumbnail))
  val watermarkedFileSink = FileIO.toFile(getFile(path, Watermarked))

  streamFan.out(0) ~> rawFileSink
  streamFan.out(1) ~> byteAccumulator ~> byteArrayFan.in
  streamFan.out(2) ~> output.in

  byteArrayFan.out(0) ~> slowThumbnailProcessing ~> thumbnailFileSink
  byteArrayFan.out(1) ~> slowWatermarkProcessing ~> watermarkedFileSink

  FlowShape(streamFan.in, output.out)
})

graph

} }

Then I wire it in to my play controller using an accumulator like this: 然后我使用这样的累加器将它连接到我的播放控制器:

val sink = Sink.head[Try[Done]]

val photoStorageParser = BodyParser { req =>
     Accumulator(sink).through(graph).map(Right.apply)
}

The problem is that my two processed file sinks aren't completing and I'm getting zero sizes for both processed files, but not the raw one. 问题是我的两个处理过的文件接收器没有完成,我得到的两个处理文件的大小都是零,但不是原始的。 My theory is that the accumulator is only waiting on one of the outputs of my fan out, so when the input stream completes and my byteAccumulator spits out the complete file, by the time the processing is finished play has got the materialized value from the output. 我的理论是累加器只等待我的扇出的一个输出,所以当输入流完成并且我的byteAccumulator吐出完整的文件时,到处理完成时,播放已从输出获得物化值。

So, my questions are: 所以,我的问题是:
Am I on the right track with this as far as my approach goes? 就我的方法而言,我是否在正确的轨道上? What is the expected behaviour for running a graph like this? 运行这样的图形的预期行为是什么? How can I bring all my sinks together to form one final sink? 如何将所有水槽组合在一起形成一个最终水槽?

Ok, after a little help (Andreas was on the right track), I've arrived at this solution which does the trick: 好吧,经过一点帮助(安德烈亚斯在正确的轨道上),我已经到达了这个解决方案,它可以解决问题:

val rawFileSink = FileIO.toFile(file)
val thumbnailFileSink = FileIO.toFile(getFile(path, Thumbnail))
val watermarkedFileSink = FileIO.toFile(getFile(path, Watermarked))

val graph = Sink.fromGraph(GraphDSL.create(rawFileSink, thumbnailFileSink, watermarkedFileSink)((_, _, _)) {
  implicit builder => (rawSink, thumbSink, waterSink) => {
    val streamFan = builder.add(Broadcast[ByteString](2))
    val byteArrayFan = builder.add(Broadcast[Array[Byte]](2))

    streamFan.out(0) ~> rawSink
    streamFan.out(1) ~> byteAccumulator ~> byteArrayFan.in

    byteArrayFan.out(0) ~> processorFlow(Thumbnail) ~> thumbSink
    byteArrayFan.out(1) ~> processorFlow(Watermarked) ~> waterSink

    SinkShape(streamFan.in)
  }
})

graph.mapMaterializedValue[Future[Try[Done]]](fs => Future.sequence(Seq(fs._1, fs._2, fs._3)).map(f => Success(Done)))

After which it's dead easy to call this from Play: 之后很容易从Play中调用它:

val photoStorageParser = BodyParser { req =>
  Accumulator(theSink).map(Right.apply)
}

def createImage(path: String) = Action(photoStorageParser) { req =>
  Created
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM