|
6 | 6 | import org.slf4j.LoggerFactory;
|
7 | 7 |
|
8 | 8 | import java.io.IOException;
|
9 |
| -import java.io.UncheckedIOException; |
10 | 9 | import java.net.URI;
|
11 | 10 | import java.nio.file.Files;
|
12 | 11 | import java.nio.file.Path;
|
|
17 | 16 | import java.util.Set;
|
18 | 17 | import java.util.concurrent.ExecutionException;
|
19 | 18 | import java.util.concurrent.Future;
|
| 19 | +import java.util.concurrent.Semaphore; |
20 | 20 |
|
21 | 21 | public final class AssetsUtils {
|
22 | 22 | private static final String INDEX_FOLDER = "indexes";
|
23 | 23 | private static final String OBJECT_FOLDER = "objects";
|
24 | 24 | private static final String ASSETS_BASE_URL = "https://resources.download.minecraft.net/";
|
25 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(AssetsUtils.class);
|
26 | 26 |
|
| 27 | + private static final String PARALLEL_DOWNLOADS_PROPERTY = "dev.lukebemish.taskgraphrunner.assets.parallel-downloads"; |
| 28 | + |
27 | 29 | // Sort from most to least indexes
|
28 | 30 | private static final Comparator<Target> ASSET_INDEX_COUNT_DESCENDING = Comparator.<Target>comparingInt(d -> d.indexes.size()).reversed();
|
29 | 31 |
|
@@ -80,40 +82,46 @@ public static Path findOrDownloadIndexAndAssets(DownloadUtils.Spec spec, String
|
80 | 82 | try (var reader = Files.newBufferedReader(targetPath)) {
|
81 | 83 | json = JsonUtils.GSON.fromJson(reader, JsonObject.class);
|
82 | 84 | }
|
83 |
| - } |
84 | 85 |
|
85 |
| - var objectsPath = assetOptions.assetRoot().resolve(OBJECT_FOLDER); |
86 |
| - var objects = json.getAsJsonObject("objects"); |
87 |
| - var targets = objects.asMap().values().stream() |
88 |
| - .distinct() // The same object can be referenced multiple times |
89 |
| - .map(entry -> { |
90 |
| - var obj = entry.getAsJsonObject(); |
91 |
| - var hash = obj.getAsJsonPrimitive("hash").getAsString(); |
92 |
| - var size = obj.getAsJsonPrimitive("size").getAsLong(); |
93 |
| - var objectPath = objectsPath.resolve(hash.substring(0, 2)).resolve(hash); |
94 |
| - var url = URI.create(ASSETS_BASE_URL + hash.substring(0, 2) + "/" + hash); |
95 |
| - var objectSpec = new DownloadUtils.Spec.ChecksumAndSize(url, hash, "SHA-1", size); |
96 |
| - return new DownloadTarget(hash, objectSpec, objectPath); |
97 |
| - }) |
98 |
| - .toList(); |
99 |
| - |
100 |
| - try (var ignored = context.lockManager().locks(targets.stream().map(t -> "assets."+t.target().getFileName().toString()).toList())) { |
101 |
| - var futures = new ArrayList<Future<?>>(); |
102 |
| - for (var target : targets) { |
103 |
| - futures.add(context.submit(() -> { |
| 86 | + var objectsPath = assetOptions.assetRoot().resolve(OBJECT_FOLDER); |
| 87 | + var objects = json.getAsJsonObject("objects"); |
| 88 | + var targets = objects.asMap().values().stream() |
| 89 | + .distinct() // The same object can be referenced multiple times |
| 90 | + .map(entry -> { |
| 91 | + var obj = entry.getAsJsonObject(); |
| 92 | + var hash = obj.getAsJsonPrimitive("hash").getAsString(); |
| 93 | + var size = obj.getAsJsonPrimitive("size").getAsLong(); |
| 94 | + var objectPath = objectsPath.resolve(hash.substring(0, 2)).resolve(hash); |
| 95 | + var url = URI.create(ASSETS_BASE_URL + hash.substring(0, 2) + "/" + hash); |
| 96 | + var objectSpec = new DownloadUtils.Spec.ChecksumAndSize(url, hash, "SHA-1", size); |
| 97 | + return new DownloadTarget(hash, objectSpec, objectPath); |
| 98 | + }) |
| 99 | + .toList(); |
| 100 | + |
| 101 | + try (var ignored1 = context.lockManager().locks(targets.stream().map(t -> "assets." + t.target().getFileName().toString()).toList())) { |
| 102 | + var futures = new ArrayList<Future<?>>(); |
| 103 | + var semaphore = new Semaphore(Integer.getInteger(PARALLEL_DOWNLOADS_PROPERTY, 8)); |
| 104 | + for (var target : targets) { |
| 105 | + futures.add(context.submit(() -> { |
| 106 | + try { |
| 107 | + semaphore.acquire(); |
| 108 | + DownloadUtils.download(target.spec(), target.target()); |
| 109 | + } catch (IOException | InterruptedException e) { |
| 110 | + LOGGER.error("Failed to download asset {} from {}", target.name(), target.spec().uri(), e); |
| 111 | + throw new RuntimeException(e); |
| 112 | + } finally { |
| 113 | + semaphore.release(); |
| 114 | + } |
| 115 | + })); |
| 116 | + } |
| 117 | + for (var future : futures) { |
104 | 118 | try {
|
105 |
| - DownloadUtils.download(target.spec(), target.target()); |
106 |
| - } catch (IOException e) { |
107 |
| - LOGGER.error("Failed to download asset {}", target.name(), e); |
108 |
| - throw new UncheckedIOException(e); |
| 119 | + future.get(); |
| 120 | + } catch (InterruptedException | ExecutionException e) { |
| 121 | + // It failed; don't keep the bad index around |
| 122 | + Files.deleteIfExists(targetPath); |
| 123 | + throw new RuntimeException(e); |
109 | 124 | }
|
110 |
| - })); |
111 |
| - } |
112 |
| - for (var future : futures) { |
113 |
| - try { |
114 |
| - future.get(); |
115 |
| - } catch (InterruptedException | ExecutionException e) { |
116 |
| - throw new RuntimeException(e); |
117 | 125 | }
|
118 | 126 | }
|
119 | 127 | }
|
|
0 commit comments