forked from akka/alpakka
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request akka#1890 from michalbogacz/file-zip-support
Added flow for creating file ZIP archive
- Loading branch information
Showing
12 changed files
with
555 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
file/src/main/scala/akka/stream/alpakka/file/impl/archive/FileByteStringSeparators.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.impl.archive | ||
|
||
import akka.annotation.InternalApi | ||
import akka.util.ByteString | ||
|
||
/** | ||
* INTERNAL API | ||
* | ||
* ArchiveZipFlow operates on ByteString. But it is required to inform ZipOutputStream when each file starts and ends. | ||
* For this, special starting and ending ByteString is added. | ||
*/ | ||
@InternalApi private[file] object FileByteStringSeparators { | ||
private val startFileWord = "$START$" | ||
private val endFileWord = "$END$" | ||
private val separator: Char = '|' | ||
|
||
def createStartingByteString(path: String): ByteString = | ||
ByteString(s"$startFileWord$separator$path") | ||
|
||
def createEndingByteString(): ByteString = | ||
ByteString(endFileWord) | ||
|
||
def isStartingByteString(b: ByteString): Boolean = | ||
b.utf8String.startsWith(startFileWord) | ||
|
||
def isEndingByteString(b: ByteString): Boolean = | ||
b.utf8String == endFileWord | ||
|
||
def getPathFromStartingByteString(b: ByteString): String = { | ||
val splitted = b.utf8String.split(separator) | ||
if (splitted.length == 1) { | ||
"" | ||
} else if (splitted.length == 2) { | ||
splitted.tail.head | ||
} else { | ||
splitted.tail.mkString(separator.toString) | ||
} | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
file/src/main/scala/akka/stream/alpakka/file/impl/archive/ZipArchiveFlow.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.impl.archive | ||
|
||
import java.util.zip.{ZipEntry, ZipOutputStream} | ||
|
||
import akka.annotation.InternalApi | ||
import akka.event.Logging | ||
import akka.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler} | ||
import akka.stream.{Attributes, FlowShape, Inlet, Outlet} | ||
import akka.util.{ByteString, ByteStringBuilder} | ||
|
||
/** | ||
* INTERNAL API | ||
*/ | ||
@InternalApi private[file] final class ZipArchiveFlowStage( | ||
val shape: FlowShape[ByteString, ByteString] | ||
) extends GraphStageLogic(shape) { | ||
|
||
private def in = shape.in | ||
private def out = shape.out | ||
|
||
private val builder = new ByteStringBuilder() | ||
private val zip = new ZipOutputStream(builder.asOutputStream) | ||
private var emptyStream = true | ||
|
||
setHandler( | ||
out, | ||
new OutHandler { | ||
override def onPull(): Unit = | ||
if (isClosed(in)) { | ||
emptyStream = true | ||
completeStage() | ||
} else { | ||
pull(in) | ||
} | ||
} | ||
) | ||
|
||
setHandler( | ||
in, | ||
new InHandler { | ||
override def onPush(): Unit = { | ||
emptyStream = false | ||
val element = grab(in) | ||
element match { | ||
case b: ByteString if FileByteStringSeparators.isStartingByteString(b) => | ||
val name = FileByteStringSeparators.getPathFromStartingByteString(b) | ||
zip.putNextEntry(new ZipEntry(name)) | ||
case b: ByteString if FileByteStringSeparators.isEndingByteString(b) => | ||
zip.closeEntry() | ||
case b: ByteString => | ||
val array = b.toArray | ||
zip.write(array, 0, array.length) | ||
} | ||
zip.flush() | ||
val result = builder.result | ||
if (result.nonEmpty) { | ||
builder.clear() | ||
push(out, result) | ||
} else { | ||
pull(in) | ||
} | ||
} | ||
|
||
override def onUpstreamFinish(): Unit = { | ||
if (!emptyStream) { | ||
zip.close() | ||
push(out, builder.result) | ||
builder.clear() | ||
} | ||
super.onUpstreamFinish() | ||
} | ||
|
||
} | ||
) | ||
|
||
} | ||
|
||
/** | ||
* INTERNAL API | ||
*/ | ||
@InternalApi private[file] final class ZipArchiveFlow extends GraphStage[FlowShape[ByteString, ByteString]] { | ||
|
||
val in: Inlet[ByteString] = Inlet(Logging.simpleName(this) + ".in") | ||
val out: Outlet[ByteString] = Outlet(Logging.simpleName(this) + ".out") | ||
|
||
override def initialAttributes: Attributes = | ||
Attributes.name(Logging.simpleName(this)) | ||
|
||
override val shape: FlowShape[ByteString, ByteString] = FlowShape(in, out) | ||
|
||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = | ||
new ZipArchiveFlowStage(shape) | ||
} |
30 changes: 30 additions & 0 deletions
30
file/src/main/scala/akka/stream/alpakka/file/impl/archive/ZipArchiveManager.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.impl.archive | ||
|
||
import akka.NotUsed | ||
import akka.annotation.InternalApi | ||
import akka.stream.alpakka.file.ArchiveMetadata | ||
import akka.stream.scaladsl.{Flow, Source} | ||
import akka.util.ByteString | ||
|
||
/** | ||
* INTERNAL API | ||
*/ | ||
@InternalApi private[file] object ZipArchiveManager { | ||
|
||
def zipFlow(): Flow[(ArchiveMetadata, Source[ByteString, Any]), ByteString, NotUsed] = { | ||
val archiveZipFlow = new ZipArchiveFlow() | ||
Flow[(ArchiveMetadata, Source[ByteString, Any])] | ||
.flatMapConcat { | ||
case (metadata, stream) => | ||
val prependElem = Source.single(FileByteStringSeparators.createStartingByteString(metadata.filePath)) | ||
val appendElem = Source.single(FileByteStringSeparators.createEndingByteString()) | ||
stream.prepend(prependElem).concat(appendElem) | ||
} | ||
.via(archiveZipFlow) | ||
} | ||
|
||
} |
31 changes: 31 additions & 0 deletions
31
file/src/main/scala/akka/stream/alpakka/file/javadsl/Archive.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.javadsl | ||
|
||
import akka.NotUsed | ||
import akka.stream.alpakka.file.{scaladsl, ArchiveMetadata} | ||
import akka.stream.javadsl.Flow | ||
import akka.util.ByteString | ||
import akka.japi.Pair | ||
import akka.stream.javadsl.Source | ||
|
||
/** | ||
* Java API. | ||
*/ | ||
object Archive { | ||
|
||
/** | ||
* Flow for compressing multiple files into one ZIP file. | ||
*/ | ||
def zip(): Flow[Pair[ArchiveMetadata, Source[ByteString, NotUsed]], ByteString, NotUsed] = | ||
Flow | ||
.create[Pair[ArchiveMetadata, Source[ByteString, NotUsed]]]() | ||
.map(func(pair => (pair.first, pair.second.asScala))) | ||
.via(scaladsl.Archive.zip().asJava) | ||
|
||
private def func[T, R](f: T => R) = new akka.japi.function.Function[T, R] { | ||
override def apply(param: T): R = f(param) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file | ||
|
||
final class ArchiveMetadata private ( | ||
val filePath: String | ||
) | ||
|
||
object ArchiveMetadata { | ||
def apply(filePath: String): ArchiveMetadata = new ArchiveMetadata(filePath) | ||
def create(filePath: String): ArchiveMetadata = new ArchiveMetadata(filePath) | ||
} |
24 changes: 24 additions & 0 deletions
24
file/src/main/scala/akka/stream/alpakka/file/scaladsl/Archive.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.scaladsl | ||
|
||
import akka.NotUsed | ||
import akka.stream.alpakka.file.ArchiveMetadata | ||
import akka.stream.alpakka.file.impl.archive.ZipArchiveManager | ||
import akka.stream.scaladsl.{Flow, Source} | ||
import akka.util.ByteString | ||
|
||
/** | ||
* Scala API. | ||
*/ | ||
object Archive { | ||
|
||
/** | ||
* Flow for compressing multiple files into one ZIP file. | ||
*/ | ||
def zip(): Flow[(ArchiveMetadata, Source[ByteString, Any]), ByteString, NotUsed] = | ||
ZipArchiveManager.zipFlow() | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright (C) 2016-2019 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package docs.javadsl; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileOutputStream; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
import java.util.zip.ZipEntry; | ||
import java.util.zip.ZipOutputStream; | ||
|
||
public class ArchiveHelper { | ||
|
||
public void createReferenceZipFile(List<Path> inputFilePaths, String resultFileName) | ||
throws Exception { | ||
FileOutputStream fout = new FileOutputStream(resultFileName); | ||
ZipOutputStream zout = new ZipOutputStream(fout); | ||
for (Path inputFilePath : inputFilePaths) { | ||
File fileToZip = inputFilePath.toFile(); | ||
FileInputStream fis = new FileInputStream(fileToZip); | ||
ZipEntry zipEntry = new ZipEntry(fileToZip.getName()); | ||
zout.putNextEntry(zipEntry); | ||
byte[] bytes = new byte[1024]; | ||
int length; | ||
while ((length = fis.read(bytes)) >= 0) { | ||
zout.write(bytes, 0, length); | ||
} | ||
fis.close(); | ||
} | ||
zout.close(); | ||
} | ||
} |
Oops, something went wrong.