|
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