-
Notifications
You must be signed in to change notification settings - Fork 643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
file: unzip introduced #2692
file: unzip introduced #2692
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright (C) 2016-2020 Lightbend Inc. <https://www.lightbend.com> | ||
*/ | ||
|
||
package akka.stream.alpakka.file.impl.archive | ||
|
||
import akka.NotUsed | ||
import akka.annotation.InternalApi | ||
import akka.stream.{Attributes, Outlet, SourceShape} | ||
import akka.stream.scaladsl.Source | ||
import akka.stream.stage.{GraphStage, GraphStageLogic, OutHandler} | ||
import akka.util.ByteString | ||
|
||
import java.io.{File, FileInputStream} | ||
import java.util.zip.{ZipEntry, ZipInputStream} | ||
|
||
case class ZipArchiveMetadata(name: String) | ||
|
||
@InternalApi class ZipEntrySource(n: ZipArchiveMetadata, f: File, chunkSize: Int) | ||
extends GraphStage[SourceShape[ByteString]] { | ||
private val out = Outlet[ByteString]("flowOut") | ||
override val shape: SourceShape[ByteString] = | ||
SourceShape(out) | ||
|
||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = | ||
new GraphStageLogic(shape) { | ||
val zis = new ZipInputStream(new FileInputStream(f)) | ||
var entry: ZipEntry = null | ||
val data = new Array[Byte](chunkSize) | ||
|
||
def seek() = { | ||
while ({ | ||
entry = zis.getNextEntry() | ||
entry != null && entry.getName != n.name | ||
}) { | ||
zis.closeEntry() | ||
} | ||
} | ||
|
||
setHandler( | ||
out, | ||
new OutHandler { | ||
override def onPull(): Unit = { | ||
if (entry == null) { | ||
seek() | ||
if (entry == null) { | ||
failStage(new Exception("After a seek the part is not found")) | ||
} | ||
} | ||
|
||
val c = zis.read(data, 0, chunkSize) | ||
if (c == -1) { | ||
completeStage() | ||
} else { | ||
push(out, ByteString.fromArray(data, 0, c)) | ||
} | ||
} | ||
} | ||
) | ||
|
||
override def postStop(): Unit = { | ||
super.postStop() | ||
zis.close() | ||
} | ||
} | ||
} | ||
|
||
@InternalApi class ZipSource(f: File, chunkSize: Int) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tg44 I see that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current source works with file seek. I needed to parallel access files. Without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I totally understand your approach. I had a look at the tar reading example now, and I think it would be good if we could have the same for zip reading as well (in addition to your implementation). What also bothers me a bit is that for writing zip files as well as for read/writing tar file we work with sources and sinks, but for reading zip files we only allow Files now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ofc. we can implement a tar reading like API too, I just haven't needed that :) I think you can easily merge the tar reading and zip file reading logic. (Sorry, but I'm really short on time nowadays, so I can't contribute that in the next weeks.) |
||
extends GraphStage[SourceShape[(ZipArchiveMetadata, Source[ByteString, NotUsed])]] { | ||
private val out = Outlet[(ZipArchiveMetadata, Source[ByteString, NotUsed])]("flowOut") | ||
override val shape: SourceShape[(ZipArchiveMetadata, Source[ByteString, NotUsed])] = | ||
SourceShape(out) | ||
|
||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = | ||
new GraphStageLogic(shape) { | ||
val zis = new ZipInputStream(new FileInputStream(f)) | ||
|
||
setHandler( | ||
out, | ||
new OutHandler { | ||
override def onPull(): Unit = { | ||
val e = zis.getNextEntry | ||
if (e != null) { | ||
val n = ZipArchiveMetadata(e.getName) | ||
zis.closeEntry() | ||
push(out, n -> Source.fromGraph(new ZipEntrySource(n, f, chunkSize))) | ||
} else { | ||
zis.close() | ||
completeStage() | ||
} | ||
} | ||
} | ||
) | ||
|
||
override def postStop(): Unit = { | ||
super.postStop() | ||
zis.close() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -12,6 +12,7 @@ | |||||
import akka.stream.Materializer; | ||||||
import akka.stream.alpakka.file.ArchiveMetadata; | ||||||
import akka.stream.alpakka.file.TarArchiveMetadata; | ||||||
import akka.stream.alpakka.file.impl.archive.ZipArchiveMetadata; | ||||||
import akka.stream.alpakka.file.javadsl.Archive; | ||||||
import akka.stream.alpakka.file.javadsl.Directory; | ||||||
import akka.stream.alpakka.testkit.javadsl.LogCapturingJunit4; | ||||||
|
@@ -106,6 +107,23 @@ public void flowShouldCreateZIPArchive() throws Exception { | |||||
Map<String, ByteString> unzip = archiveHelper.unzip(resultFileContent); | ||||||
|
||||||
assertThat(inputFiles, is(unzip)); | ||||||
Path target = Files.createTempDirectory("alpakka-tar-"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
|
||||||
// #sample-zip-read | ||||||
Archive.zipReader(Paths.get("logo.zip").toFile()) | ||||||
.mapAsync( | ||||||
4, | ||||||
pair -> { | ||||||
ZipArchiveMetadata metadata = pair.first(); | ||||||
Path targetFile = target.resolve(metadata.name()); | ||||||
targetFile.toFile().getParentFile().mkdirs(); //missing error handler | ||||||
Source<ByteString, NotUsed> fSource = pair.second(); | ||||||
// create the target directory | ||||||
return fSource | ||||||
.runWith(FileIO.toPath(targetFile), system) | ||||||
.thenApply(io -> Done.done()); | ||||||
}); | ||||||
// #sample-zip-read | ||||||
|
||||||
// cleanup | ||||||
new File("logo.zip").delete(); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is user-facing API and should preferably live in
akka.stream.alpakka.file
instead. Add agetName
for Java folks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!