1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2020 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.technical
21 import java.io.File
22 import java.nio.charset.Charset
23 import java.util.concurrent.Executors
24 
25 import akka.http.javadsl.model.headers.Accept
26 import akka.http.scaladsl.Http
27 import akka.http.scaladsl.model._
28 import akka.stream.scaladsl.FileIO
29 import grizzled.slf4j.Logging
30 import kamon.annotation.api.Trace
31 import org.make.api.technical.ExecutorServiceHelper._
32 
33 import scala.concurrent.duration.DurationInt
34 import scala.concurrent.{ExecutionContext, Future}
35 import scala.util.{Failure, Success, Try}
36 
37 trait DefaultDownloadServiceComponent extends DownloadServiceComponent with Logging {
38   this: ActorSystemComponent =>
39 
40   override lazy val downloadService: DownloadService = new DefaultDownloadService
41   private val maxRedirectCount = 3
42 
43   class DefaultDownloadService extends DownloadService {
44 
45     implicit val ec: ExecutionContext =
46       Executors.newFixedThreadPool(4).instrument("avatar-downloader").toExecutionContext
47 
48     @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
49     @Trace(operationName = "client-downloadImage")
50     override def downloadImage(
51       imageUrl: String,
52       destFn: ContentType => File,
53       redirectCount: Int = 0
54     ): Future[(ContentType, File)] = {
55       Try(HttpRequest(uri = Uri(imageUrl), headers = Seq(Accept.create(MediaRanges.`image/*`)))) match {
56         case Failure(e) => Future.failed(e)
57         case Success(req) =>
58           Http()(actorSystem)
59             .singleRequest(req)
60             .flatMap {
61               case response if response.status.intValue() >= 400 && response.status.intValue() < 500 =>
62                 response.discardEntityBytes()
63                 Future.failed(ImageUnavailable(imageUrl))
64               case response if response.status.isFailure() =>
65                 response.entity.toStrict(2.second).flatMap { entity =>
66                   val body = entity.data.decodeString(Charset.forName("UTF-8"))
67                   val code = response.status.value
68                   Future.failed(
69                     new IllegalStateException(s"URL failed with status code: $code, from: $imageUrl with body: $body")
70                   )
71                 }
72               case response if response.status.isRedirection() =>
73                 response.header[headers.Location] match {
74                   case Some(location) if redirectCount < maxRedirectCount =>
75                     downloadImage(location.uri.toString, destFn, redirectCount + 1)
76                   case None =>
77                     response.discardEntityBytes()
78                     Future.failed(new IllegalStateException(s"URL is a redirect without location: $imageUrl"))
79                   case _ =>
80                     response.discardEntityBytes()
81                     Future.failed(new IllegalStateException(s"Max redirect count reached with url: $imageUrl"))
82                 }
83               case response if !response.entity.httpEntity.contentType.mediaType.isImage =>
84                 response.discardEntityBytes()
85                 Future.failed(new IllegalStateException(s"URL does not refer to an image: $imageUrl"))
86               case response =>
87                 val contentType = response.entity.httpEntity.contentType
88                 val dest = destFn(contentType)
89                 response.entity.dataBytes
90                   .runWith(FileIO.toPath(dest.toPath))
91                   .map(_ => contentType -> dest)
92             }
93       }
94     }
95   }
96 }
97 
98 final case class ImageUnavailable(imageUrl: String) extends Exception(s"Image not found for URL $imageUrl.")
Line Stmt Id Pos Tree Symbol Tests Code
41 24889 1505 - 1506 Literal <nosymbol> org.make.api.technical.downloadservicetest 3
46 23676 1612 - 1675 Apply org.make.api.technical.ExecutorServiceHelper.RichExecutorService.instrument org.make.api.technical.downloadservicetest org.make.api.technical.ExecutorServiceHelper.RichExecutorService(java.util.concurrent.Executors.newFixedThreadPool(4)).instrument("avatar-downloader")
46 27345 1612 - 1694 Select org.make.api.technical.ExecutorServiceHelper.RichExecutorService.toExecutionContext org.make.api.technical.downloadservicetest org.make.api.technical.ExecutorServiceHelper.RichExecutorService(org.make.api.technical.ExecutorServiceHelper.RichExecutorService(java.util.concurrent.Executors.newFixedThreadPool(4)).instrument("avatar-downloader")).toExecutionContext
55 25566 2023 - 2064 Apply scala.collection.SeqFactory.Delegate.apply org.make.api.technical.downloadservicetest scala.`package`.Seq.apply[akka.http.javadsl.model.headers.Accept](akka.http.javadsl.model.headers.Accept.create(akka.http.scaladsl.model.MediaRanges.image/*))
55 25189 1998 - 2011 Apply akka.http.scaladsl.model.Uri.apply org.make.api.technical.downloadservicetest akka.http.scaladsl.model.Uri.apply(imageUrl)
55 24812 1980 - 1980 Select akka.http.scaladsl.model.HttpRequest.apply$default$5 org.make.api.technical.downloadservicetest akka.http.scaladsl.model.HttpRequest.apply$default$5
55 22718 2041 - 2062 Select akka.http.scaladsl.model.MediaRanges.image/* org.make.api.technical.downloadservicetest akka.http.scaladsl.model.MediaRanges.image/*
55 23687 1980 - 2065 Apply akka.http.scaladsl.model.HttpRequest.apply org.make.api.technical.downloadservicetest akka.http.scaladsl.model.HttpRequest.apply(x$3, x$1, x$2, x$4, x$5)
55 27057 1980 - 1980 Select akka.http.scaladsl.model.HttpRequest.apply$default$4 org.make.api.technical.downloadservicetest akka.http.scaladsl.model.HttpRequest.apply$default$4
55 27811 2027 - 2063 Apply akka.http.javadsl.model.headers.Accept.create org.make.api.technical.downloadservicetest akka.http.javadsl.model.headers.Accept.create(akka.http.scaladsl.model.MediaRanges.image/*)
55 27353 1976 - 2066 Apply scala.util.Try.apply org.make.api.technical.downloadservicetest scala.util.Try.apply[akka.http.scaladsl.model.HttpRequest]({ <artifact> val x$1: akka.http.scaladsl.model.Uri = akka.http.scaladsl.model.Uri.apply(imageUrl); <artifact> val x$2: Seq[akka.http.javadsl.model.headers.Accept] @scala.reflect.internal.annotations.uncheckedBounds = scala.`package`.Seq.apply[akka.http.javadsl.model.headers.Accept](akka.http.javadsl.model.headers.Accept.create(akka.http.scaladsl.model.MediaRanges.image/*)); <artifact> val x$3: akka.http.scaladsl.model.HttpMethod = akka.http.scaladsl.model.HttpRequest.apply$default$1; <artifact> val x$4: akka.http.scaladsl.model.RequestEntity = akka.http.scaladsl.model.HttpRequest.apply$default$4; <artifact> val x$5: akka.http.scaladsl.model.HttpProtocol = akka.http.scaladsl.model.HttpRequest.apply$default$5; akka.http.scaladsl.model.HttpRequest.apply(x$3, x$1, x$2, x$4, x$5) })
55 23231 1980 - 1980 Select akka.http.scaladsl.model.HttpRequest.apply$default$1 org.make.api.technical.downloadservicetest akka.http.scaladsl.model.HttpRequest.apply$default$1
56 25116 2102 - 2118 Apply scala.concurrent.Future.failed org.make.api.technical.downloadservicetest scala.concurrent.Future.failed[Nothing](e)
58 27746 2158 - 2177 Apply akka.http.scaladsl.Http.apply org.make.api.technical.downloadservicetest akka.http.scaladsl.Http.apply()(DefaultDownloadServiceComponent.this.actorSystem)
58 22780 2165 - 2176 Select org.make.api.technical.ActorSystemComponent.actorSystem org.make.api.technical.downloadservicetest DefaultDownloadServiceComponent.this.actorSystem
59 26991 2191 - 2191 Select akka.http.scaladsl.HttpExt.singleRequest$default$4 org.make.api.technical.downloadservicetest qual$1.singleRequest$default$4
59 24658 2158 - 2209 Apply akka.http.scaladsl.HttpExt.singleRequest org.make.api.technical.downloadservicetest qual$1.singleRequest(x$6, x$7, x$8, x$9)
59 23240 2191 - 2191 Select akka.http.scaladsl.HttpExt.singleRequest$default$3 org.make.api.technical.downloadservicetest qual$1.singleRequest$default$3
59 25414 2191 - 2191 Select akka.http.scaladsl.HttpExt.singleRequest$default$2 org.make.api.technical.downloadservicetest qual$1.singleRequest$default$2
60 24984 2158 - 4131 ApplyToImplicitArgs scala.concurrent.Future.flatMap org.make.api.technical.downloadservicetest { <artifact> val qual$1: akka.http.scaladsl.HttpExt = akka.http.scaladsl.Http.apply()(DefaultDownloadServiceComponent.this.actorSystem); <artifact> val x$6: akka.http.scaladsl.model.HttpRequest = req; <artifact> val x$7: akka.http.scaladsl.HttpsConnectionContext = qual$1.singleRequest$default$2; <artifact> val x$8: akka.http.scaladsl.settings.ConnectionPoolSettings = qual$1.singleRequest$default$3; <artifact> val x$9: akka.event.LoggingAdapter = qual$1.singleRequest$default$4; qual$1.singleRequest(x$6, x$7, x$8, x$9) }.flatMap[(akka.http.scaladsl.model.ContentType, java.io.File)](((x0$1: akka.http.scaladsl.model.HttpResponse) => x0$1 match { case (response @ _) if response.status.intValue().>=(400).&&(response.status.intValue().<(500)) => { model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)); scala.concurrent.Future.failed[Nothing](ImageUnavailable.apply(imageUrl)) } case (response @ _) if response.status.isFailure() => response.entity.toStrict(scala.concurrent.duration.`package`.DurationInt(2).second)(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)).flatMap[Nothing](((entity: akka.http.scaladsl.model.HttpEntity.Strict) => { val body: String = entity.data.decodeString(java.nio.charset.Charset.forName("UTF-8")); val code: String = response.status.value; scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL failed with status code: ".+(code).+(", from: ").+(imageUrl).+(" with body: ").+(body): String))) }))(DefaultDownloadService.this.ec) case (response @ _) if response.status.isRedirection() => response.header[akka.http.scaladsl.model.headers.Location]((ClassTag.apply[akka.http.scaladsl.model.headers.Location](classOf[akka.http.scaladsl.model.headers.Location]): scala.reflect.ClassTag[akka.http.scaladsl.model.headers.Location])) match { case (value: akka.http.scaladsl.model.headers.Location): Some[akka.http.scaladsl.model.headers.Location]((location @ _)) if redirectCount.<(DefaultDownloadServiceComponent.this.maxRedirectCount) => DefaultDownloadService.this.downloadImage(location.uri.toString(), destFn, redirectCount.+(1)) case scala.None => { model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)); scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL is a redirect without location: ".+(imageUrl): String))) } case _ => { model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)); scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("Max redirect count reached with url: ".+(imageUrl): String))) } } case (response @ _) if model.this.HttpEntity.HttpEntityScalaDSLSugar(response.entity).httpEntity.contentType.mediaType.isImage.unary_! => { model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)); scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL does not refer to an image: ".+(imageUrl): String))) } case (response @ _) => { val contentType: akka.http.scaladsl.model.ContentType = model.this.HttpEntity.HttpEntityScalaDSLSugar(response.entity).httpEntity.contentType; val dest: java.io.File = destFn.apply(contentType); response.entity.dataBytes.runWith[scala.concurrent.Future[akka.stream.IOResult]](akka.stream.scaladsl.FileIO.toPath(dest.toPath(), akka.stream.scaladsl.FileIO.toPath$default$2))(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)).map[(akka.http.scaladsl.model.ContentType, java.io.File)](((x$1: akka.stream.IOResult) => scala.Predef.ArrowAssoc[akka.http.scaladsl.model.ContentType](contentType).->[java.io.File](dest)))(DefaultDownloadService.this.ec) } }))(DefaultDownloadService.this.ec)
60 27167 2231 - 2231 Select org.make.api.technical.DefaultDownloadServiceComponent.DefaultDownloadService.ec org.make.api.technical.downloadservicetest DefaultDownloadService.this.ec
61 23613 2294 - 2297 Literal <nosymbol> 400
61 27284 2301 - 2333 Apply scala.Int.< response.status.intValue().<(500)
61 25125 2264 - 2333 Apply scala.Boolean.&& response.status.intValue().>=(400).&&(response.status.intValue().<(500))
62 25420 2353 - 2382 ApplyToImplicitArgs akka.http.scaladsl.model.HttpMessage.HttpMessageScalaDSLSugar.discardEntityBytes model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem))
62 26535 2380 - 2380 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
62 22705 2380 - 2380 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
63 23164 2413 - 2439 Apply org.make.api.technical.ImageUnavailable.apply ImageUnavailable.apply(imageUrl)
63 26844 2399 - 2440 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](ImageUnavailable.apply(imageUrl))
64 24587 2472 - 2499 Apply akka.http.scaladsl.model.StatusCode.isFailure response.status.isFailure()
65 23670 2544 - 2545 Literal <nosymbol> 2
65 27432 2519 - 2894 ApplyToImplicitArgs scala.concurrent.Future.flatMap response.entity.toStrict(scala.concurrent.duration.`package`.DurationInt(2).second)(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)).flatMap[Nothing](((entity: akka.http.scaladsl.model.HttpEntity.Strict) => { val body: String = entity.data.decodeString(java.nio.charset.Charset.forName("UTF-8")); val code: String = response.status.value; scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL failed with status code: ".+(code).+(", from: ").+(imageUrl).+(" with body: ").+(body): String))) }))(DefaultDownloadService.this.ec)
65 27291 2544 - 2552 Select scala.concurrent.duration.DurationConversions.second scala.concurrent.duration.`package`.DurationInt(2).second
65 25053 2543 - 2543 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
65 22714 2543 - 2543 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
65 23683 2562 - 2562 Select org.make.api.technical.DefaultDownloadServiceComponent.DefaultDownloadService.ec DefaultDownloadService.this.ec
66 26540 2628 - 2652 Apply java.nio.charset.Charset.forName java.nio.charset.Charset.forName("UTF-8")
66 25559 2603 - 2653 Apply akka.util.ByteString.decodeString entity.data.decodeString(java.nio.charset.Charset.forName("UTF-8"))
67 23174 2683 - 2704 Select akka.http.scaladsl.model.StatusCode.value response.status.value
68 24597 2723 - 2876 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL failed with status code: ".+(code).+(", from: ").+(imageUrl).+(" with body: ").+(body): String)))
69 26776 2758 - 2856 Apply java.lang.IllegalStateException.<init> new java.lang.IllegalStateException(("URL failed with status code: ".+(code).+(", from: ").+(imageUrl).+(" with body: ").+(body): String))
72 24907 2926 - 2957 Apply akka.http.scaladsl.model.StatusCode.isRedirection response.status.isRedirection()
73 22642 2977 - 3010 ApplyToImplicitArgs akka.http.scaladsl.model.HttpMessage.header response.header[akka.http.scaladsl.model.headers.Location]((ClassTag.apply[akka.http.scaladsl.model.headers.Location](classOf[akka.http.scaladsl.model.headers.Location]): scala.reflect.ClassTag[akka.http.scaladsl.model.headers.Location]))
74 26690 3076 - 3092 Select org.make.api.technical.DefaultDownloadServiceComponent.maxRedirectCount DefaultDownloadServiceComponent.this.maxRedirectCount
74 25574 3060 - 3092 Apply scala.Int.< redirectCount.<(DefaultDownloadServiceComponent.this.maxRedirectCount)
75 23319 3130 - 3151 Apply akka.http.scaladsl.model.Uri.toString location.uri.toString()
75 26784 3161 - 3178 Apply scala.Int.+ redirectCount.+(1)
75 24604 3116 - 3179 Apply org.make.api.technical.DefaultDownloadServiceComponent.DefaultDownloadService.downloadImage DefaultDownloadService.this.downloadImage(location.uri.toString(), destFn, redirectCount.+(1))
77 25036 3231 - 3260 ApplyToImplicitArgs akka.http.scaladsl.model.HttpMessage.HttpMessageScalaDSLSugar.discardEntityBytes model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem))
77 27446 3258 - 3258 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
77 22555 3258 - 3258 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
78 22653 3295 - 3370 Apply java.lang.IllegalStateException.<init> new java.lang.IllegalStateException(("URL is a redirect without location: ".+(imageUrl): String))
78 26700 3281 - 3371 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL is a redirect without location: ".+(imageUrl): String)))
80 25496 3447 - 3447 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
80 23160 3447 - 3447 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
80 26922 3420 - 3449 ApplyToImplicitArgs akka.http.scaladsl.model.HttpMessage.HttpMessageScalaDSLSugar.discardEntityBytes model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem))
81 24533 3484 - 3560 Apply java.lang.IllegalStateException.<init> new java.lang.IllegalStateException(("Max redirect count reached with url: ".+(imageUrl): String))
81 22568 3470 - 3561 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("Max redirect count reached with url: ".+(imageUrl): String)))
83 27368 3612 - 3627 Select akka.http.scaladsl.model.HttpResponse.entity response.entity
83 25047 3611 - 3668 Select scala.Boolean.unary_! model.this.HttpEntity.HttpEntityScalaDSLSugar(response.entity).httpEntity.contentType.mediaType.isImage.unary_!
84 22867 3715 - 3715 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
84 26629 3715 - 3715 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
84 24457 3688 - 3717 ApplyToImplicitArgs akka.http.scaladsl.model.HttpMessage.HttpMessageScalaDSLSugar.discardEntityBytes model.this.HttpMessage.HttpMessageScalaDSLSugar(response).discardEntityBytes()(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem))
85 23091 3748 - 3819 Apply java.lang.IllegalStateException.<init> new java.lang.IllegalStateException(("URL does not refer to an image: ".+(imageUrl): String))
85 26933 3734 - 3820 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](new java.lang.IllegalStateException(("URL does not refer to an image: ".+(imageUrl): String)))
87 22491 3886 - 3924 Select akka.http.scaladsl.model.HttpEntity.contentType model.this.HttpEntity.HttpEntityScalaDSLSugar(response.entity).httpEntity.contentType
87 24592 3886 - 3901 Select akka.http.scaladsl.model.HttpResponse.entity response.entity
88 27226 3952 - 3971 Apply scala.Function1.apply destFn.apply(contentType)
90 23098 4040 - 4040 ApplyToImplicitArgs akka.stream.Materializer.matFromSystem stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)
90 24375 4040 - 4040 Select org.make.api.technical.ActorSystemComponent.actorSystem DefaultDownloadServiceComponent.this.actorSystem
90 26474 4041 - 4067 Apply akka.stream.scaladsl.FileIO.toPath akka.stream.scaladsl.FileIO.toPath(dest.toPath(), akka.stream.scaladsl.FileIO.toPath$default$2)
90 22801 4048 - 4048 Select akka.stream.scaladsl.FileIO.toPath$default$2 akka.stream.scaladsl.FileIO.toPath$default$2
90 25058 4055 - 4066 Apply java.io.File.toPath dest.toPath()
91 22324 3988 - 4117 ApplyToImplicitArgs scala.concurrent.Future.map response.entity.dataBytes.runWith[scala.concurrent.Future[akka.stream.IOResult]](akka.stream.scaladsl.FileIO.toPath(dest.toPath(), akka.stream.scaladsl.FileIO.toPath$default$2))(stream.this.Materializer.matFromSystem(DefaultDownloadServiceComponent.this.actorSystem)).map[(akka.http.scaladsl.model.ContentType, java.io.File)](((x$1: akka.stream.IOResult) => scala.Predef.ArrowAssoc[akka.http.scaladsl.model.ContentType](contentType).->[java.io.File](dest)))(DefaultDownloadService.this.ec)
91 24521 4091 - 4091 Select org.make.api.technical.DefaultDownloadServiceComponent.DefaultDownloadService.ec DefaultDownloadService.this.ec
91 26940 4097 - 4116 Apply scala.Predef.ArrowAssoc.-> scala.Predef.ArrowAssoc[akka.http.scaladsl.model.ContentType](contentType).->[java.io.File](dest)