|
18 | 18 |
|
19 | 19 | package airsquared.blobsaver.app; |
20 | 20 |
|
21 | | -import airsquared.blobsaver.app.natives.Libfragmentzip; |
22 | 21 | import com.sun.jna.Platform; |
23 | 22 | import javafx.scene.Node; |
24 | 23 | import javafx.scene.control.Alert; |
|
35 | 34 | import javafx.scene.paint.Color; |
36 | 35 | import javafx.stage.DirectoryChooser; |
37 | 36 | import javafx.stage.Window; |
| 37 | +import org.apache.commons.compress.archivers.zip.ZipFile; |
38 | 38 | import org.json.JSONArray; |
39 | 39 | import org.json.JSONException; |
40 | 40 | import org.json.JSONObject; |
41 | 41 |
|
42 | | -import java.io.BufferedReader; |
43 | | -import java.io.File; |
44 | | -import java.io.IOException; |
45 | | -import java.io.InputStreamReader; |
46 | | -import java.io.PrintWriter; |
47 | | -import java.io.StringWriter; |
48 | | -import java.io.UncheckedIOException; |
| 42 | +import java.io.*; |
49 | 43 | import java.net.URL; |
50 | 44 | import java.net.URLConnection; |
| 45 | +import java.nio.ByteBuffer; |
| 46 | +import java.nio.channels.Channels; |
| 47 | +import java.nio.channels.ReadableByteChannel; |
| 48 | +import java.nio.channels.SeekableByteChannel; |
| 49 | +import java.nio.file.Files; |
| 50 | +import java.nio.file.Path; |
| 51 | +import java.nio.file.StandardCopyOption; |
51 | 52 | import java.util.ArrayList; |
52 | 53 | import java.util.List; |
53 | 54 | import java.util.Objects; |
@@ -358,15 +359,100 @@ public String toString() { |
358 | 359 | } |
359 | 360 | } |
360 | 361 |
|
361 | | - static File extractBuildManifest(String url) throws IOException { |
362 | | - File buildManifest = File.createTempFile("BuildManifest", ".plist"); |
363 | | - System.out.println("Extracting build manifest from " + url); |
364 | | - int err = Libfragmentzip.downloadFile(url, "BuildManifest.plist", buildManifest.getAbsolutePath()); |
365 | | - if (err != 0) { |
366 | | - throw new IOException("problem with libfragmentzip download: code=" + err); |
| 362 | + static Path extractBuildManifest(String ipswUrl) throws IOException { |
| 363 | + Path buildManifest = Files.createTempFile("BuildManifest", ".plist"); |
| 364 | + try (ZipFile ipsw = new ZipFile(new HttpChannel(new URL(ipswUrl)), "ipsw", "UTF8", true, true); |
| 365 | + InputStream is = ipsw.getInputStream(ipsw.getEntry("BuildManifest.plist"))) { |
| 366 | + Files.copy(is, buildManifest, StandardCopyOption.REPLACE_EXISTING); |
367 | 367 | } |
368 | 368 | System.out.println("Extracted to " + buildManifest); |
369 | | - return buildManifest; |
| 369 | + return buildManifest.toRealPath(); |
| 370 | + } |
| 371 | + |
| 372 | + /** |
| 373 | + * Source: https://github.com/jcodec/jcodec/blob/6e1ec651eca92d21b41f9790143a0e6e4d26811e/android/src/main/org/jcodec/common/io/HttpChannel.java |
| 374 | + * |
| 375 | + * @author The JCodec project |
| 376 | + */ |
| 377 | + private static final class HttpChannel implements SeekableByteChannel { |
| 378 | + |
| 379 | + private final URL url; |
| 380 | + private ReadableByteChannel ch; |
| 381 | + private long pos; |
| 382 | + private long length; |
| 383 | + |
| 384 | + public HttpChannel(URL url) { |
| 385 | + this.url = url; |
| 386 | + } |
| 387 | + |
| 388 | + @Override |
| 389 | + public long position() { |
| 390 | + return pos; |
| 391 | + } |
| 392 | + |
| 393 | + @Override |
| 394 | + public SeekableByteChannel position(long newPosition) throws IOException { |
| 395 | + if (newPosition == pos) { |
| 396 | + return this; |
| 397 | + } else if (ch != null) { |
| 398 | + ch.close(); |
| 399 | + ch = null; |
| 400 | + } |
| 401 | + pos = newPosition; |
| 402 | + return this; |
| 403 | + } |
| 404 | + |
| 405 | + @Override |
| 406 | + public long size() throws IOException { |
| 407 | + ensureOpen(); |
| 408 | + return length; |
| 409 | + } |
| 410 | + |
| 411 | + @Override |
| 412 | + public SeekableByteChannel truncate(long size) { |
| 413 | + throw new UnsupportedOperationException("Truncate on HTTP is not supported."); |
| 414 | + } |
| 415 | + |
| 416 | + @Override |
| 417 | + public int read(ByteBuffer buffer) throws IOException { |
| 418 | + ensureOpen(); |
| 419 | + int read = ch.read(buffer); |
| 420 | + if (read != -1) |
| 421 | + pos += read; |
| 422 | + return read; |
| 423 | + } |
| 424 | + |
| 425 | + @Override |
| 426 | + public int write(ByteBuffer buffer) { |
| 427 | + throw new UnsupportedOperationException("Write to HTTP is not supported."); |
| 428 | + } |
| 429 | + |
| 430 | + @Override |
| 431 | + public boolean isOpen() { |
| 432 | + return ch != null && ch.isOpen(); |
| 433 | + } |
| 434 | + |
| 435 | + @Override |
| 436 | + public void close() throws IOException { |
| 437 | + ch.close(); |
| 438 | + } |
| 439 | + |
| 440 | + private void ensureOpen() throws IOException { |
| 441 | + if (ch == null) { |
| 442 | + URLConnection connection = url.openConnection(); |
| 443 | + if (pos > 0) |
| 444 | + connection.addRequestProperty("Range", "bytes=" + pos + "-"); |
| 445 | + ch = Channels.newChannel(connection.getInputStream()); |
| 446 | + String resp = connection.getHeaderField("Content-Range"); |
| 447 | + if (resp != null) { |
| 448 | + length = Long.parseLong(resp.split("/")[1]); |
| 449 | + } else { |
| 450 | + resp = connection.getHeaderField("Content-Length"); |
| 451 | + length = Long.parseLong(resp); |
| 452 | + } |
| 453 | + } |
| 454 | + } |
| 455 | + |
370 | 456 | } |
371 | 457 |
|
372 | 458 | static String exceptionToString(Throwable t) { |
|
0 commit comments