|
18 | 18 | import com.google.common.collect.ImmutableSet; |
19 | 19 | import com.google.common.collect.Lists; |
20 | 20 | import com.google.common.collect.Maps; |
21 | | - |
22 | 21 | import org.slf4j.Logger; |
23 | 22 | import org.slf4j.LoggerFactory; |
24 | 23 | import org.terasology.config.Config; |
|
29 | 28 | import org.terasology.engine.module.RemoteModuleExtension; |
30 | 29 | import org.terasology.engine.paths.PathManager; |
31 | 30 | import org.terasology.math.Vector2i; |
| 31 | +import org.terasology.module.DependencyInfo; |
32 | 32 | import org.terasology.module.DependencyResolver; |
33 | 33 | import org.terasology.module.Module; |
34 | 34 | import org.terasology.module.ModuleLoader; |
|
53 | 53 | import java.io.IOException; |
54 | 54 | import java.net.URL; |
55 | 55 | import java.nio.file.Path; |
| 56 | +import java.util.ArrayList; |
56 | 57 | import java.util.Collections; |
57 | 58 | import java.util.Comparator; |
| 59 | +import java.util.LinkedHashMap; |
| 60 | +import java.util.LinkedList; |
58 | 61 | import java.util.List; |
59 | 62 | import java.util.Map; |
60 | 63 | import java.util.Set; |
61 | | -import java.util.function.Predicate; |
| 64 | +import java.util.concurrent.Callable; |
| 65 | +import java.util.stream.Collectors; |
62 | 66 |
|
63 | 67 | /** |
64 | 68 | * @author Immortius |
65 | 69 | */ |
66 | 70 | public class SelectModulesScreen extends CoreScreenLayer { |
67 | 71 |
|
68 | 72 | private static final Logger logger = LoggerFactory.getLogger(SelectModulesScreen.class); |
| 73 | + public static final Name ENGINE_MODULE_NAME = new Name("engine"); |
69 | 74 |
|
70 | 75 | @In |
71 | 76 | private ModuleManager moduleManager; |
@@ -219,8 +224,9 @@ public String get() { |
219 | 224 | description.bindText(new ReadOnlyBinding<String>() { |
220 | 225 | @Override |
221 | 226 | public String get() { |
222 | | - if (moduleInfoBinding.get() != null) { |
223 | | - return moduleInfoBinding.get().getDescription().toString(); |
| 227 | + ModuleMetadata moduleMetadata = moduleInfoBinding.get(); |
| 228 | + if (moduleMetadata != null) { |
| 229 | + return moduleMetadata.getDescription().toString(); |
224 | 230 | } |
225 | 231 | return ""; |
226 | 232 | } |
@@ -300,46 +306,20 @@ public void onActivated(UIWidget button) { |
300 | 306 | if (moduleList.getSelection() != null) { |
301 | 307 |
|
302 | 308 | ModuleSelectionInfo info = moduleList.getSelection(); |
303 | | - startDownload(info); |
| 309 | + startDownloadingNewestModulesRequiredFor(info); |
304 | 310 | } |
305 | 311 | } |
306 | 312 | }); |
307 | 313 |
|
308 | | - Predicate<ModuleSelectionInfo> canDownload = info -> info != null && !info.isPresent(); |
309 | | - Predicate<ModuleSelectionInfo> canUpdate = info -> { |
310 | | - if (info != null) { |
311 | | - Module online = info.getOnlineVersion(); |
312 | | - if (online != null) { |
313 | | - return online.getVersion().compareTo(info.getLatestVersion().getVersion()) > 0; |
314 | | - } |
315 | | - return false; |
316 | | - } |
317 | | - return false; |
318 | | - }; |
319 | | - |
320 | | - downloadButton.bindEnabled(new ReadOnlyBinding<Boolean>() { |
321 | | - @Override |
322 | | - public Boolean get() { |
323 | | - ModuleSelectionInfo info = moduleList.getSelection(); |
324 | | - if (canDownload.test(info)) { |
325 | | - return true; |
326 | | - } |
327 | | - |
328 | | - return canUpdate.test(info); |
329 | | - } |
330 | | - }); |
331 | 314 | downloadButton.bindText(new ReadOnlyBinding<String>() { |
332 | | - |
333 | 315 | @Override |
334 | 316 | public String get() { |
335 | 317 | ModuleSelectionInfo info = moduleList.getSelection(); |
336 | | - if (canDownload.test(info)) { |
| 318 | + if (info != null && !info.isPresent()) { |
337 | 319 | return "Download"; |
338 | | - } |
339 | | - if (canUpdate.test(info)) { |
| 320 | + } else { |
340 | 321 | return "Update"; |
341 | 322 | } |
342 | | - return "Download"; // button should be disabled |
343 | 323 | } |
344 | 324 | }); |
345 | 325 | } |
@@ -368,37 +348,169 @@ public void onActivated(UIWidget button) { |
368 | 348 | }); |
369 | 349 | } |
370 | 350 |
|
371 | | - private void startDownload(ModuleSelectionInfo info) { |
372 | | - final WaitPopup<Path> popup = getManager().pushScreen(WaitPopup.ASSET_URI, WaitPopup.class); |
373 | | - popup.setMessage("Downloading Module", "Please wait ..."); |
| 351 | + private void startDownloadingNewestModulesRequiredFor(ModuleSelectionInfo moduleMetadata) { |
| 352 | + List<ModuleSelectionInfo> modulesToDownload; |
| 353 | + try { |
| 354 | + modulesToDownload = getModulesRequiredToDownloadFor(moduleMetadata); |
| 355 | + } catch (DependencyResolutionFailed e) { |
| 356 | + MessagePopup messagePopup = getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class); |
| 357 | + messagePopup.setMessage("Depedency resolution failed", e.getMessage()); |
| 358 | + return; |
| 359 | + } |
| 360 | + |
| 361 | + LinkedHashMap<URL, Path> urlToTargetMap = determineDownloadUrlsFor(modulesToDownload); |
| 362 | + |
| 363 | + ConfirmPopup confirmPopup = getManager().pushScreen(ConfirmPopup.ASSET_URI, ConfirmPopup.class); |
| 364 | + confirmPopup.setMessage("Confirm Download", modulesToDownload.size() +" modules will be downloaded"); |
| 365 | + confirmPopup.setOkHandler(() -> downloadModules(urlToTargetMap)); |
| 366 | + } |
374 | 367 |
|
| 368 | + private void downloadModules(LinkedHashMap<URL, Path> urlToTargetMap) { |
| 369 | + final WaitPopup<List<Path>> popup = getManager().pushScreen(WaitPopup.ASSET_URI, WaitPopup.class); |
| 370 | + ModuleLoader loader = new ModuleLoader(moduleManager.getModuleMetadataReader()); |
| 371 | + loader.setModuleInfoPath(TerasologyConstants.MODULE_INFO_FILENAME); |
| 372 | + popup.onSuccess(paths -> { |
| 373 | + for (Path filePath: paths) { |
| 374 | + try { |
| 375 | + Module module = loader.load(filePath); |
| 376 | + modulesLookup.get(module.getId()).setLocalVersion(module); |
| 377 | + moduleManager.getRegistry().add(module); |
| 378 | + } catch (IOException e) { |
| 379 | + logger.warn("Could not load module {}", filePath.getFileName(), e); |
| 380 | + return; |
| 381 | + } |
| 382 | + updateValidToSelect(); |
| 383 | + } |
| 384 | + }); |
375 | 385 | ProgressListener progressListener = progress -> |
376 | | - popup.setMessage("Updating Preview", String.format("Please wait ... %d%%", (int) (progress * 100f))); |
377 | | - |
378 | | - ModuleMetadata meta = info.getOnlineVersion().getMetadata(); |
379 | | - String version = meta.getVersion().toString(); |
380 | | - String id = meta.getId().toString(); |
381 | | - URL url = RemoteModuleExtension.getDownloadUrl(meta); |
382 | | - popup.onSuccess(filePath -> { |
383 | | - ModuleLoader loader = new ModuleLoader(moduleManager.getModuleMetadataReader()); |
384 | | - loader.setModuleInfoPath(TerasologyConstants.MODULE_INFO_FILENAME); |
385 | | - try { |
386 | | - Module module = loader.load(filePath); |
387 | | - info.setLocalVersion(module); |
388 | | - moduleManager.getRegistry().add(module); |
389 | | - updateValidToSelect(); |
390 | | - } catch (IOException e) { |
391 | | - logger.warn("Could not load module '{}:{}'", id, version, e); |
| 386 | + popup.setMessage("Downloading required modules", String.format("Please wait ... %d%%", (int) (progress * 100f))); |
| 387 | + // to ensure that the intial message gets set: |
| 388 | + progressListener.onProgress(0); |
| 389 | + MultiFileDownloader operation = new MultiFileDownloader(urlToTargetMap, progressListener); |
| 390 | + popup.startOperation(operation, true); |
| 391 | + } |
| 392 | + |
| 393 | + private LinkedHashMap<URL, Path> determineDownloadUrlsFor(List<ModuleSelectionInfo> modulesToDownload) { |
| 394 | + LinkedHashMap<URL, Path> urlToTargetMap = Maps.newLinkedHashMap(); |
| 395 | + for (ModuleSelectionInfo moduleSelectionInfo: modulesToDownload) { |
| 396 | + ModuleMetadata metaData = moduleSelectionInfo.getOnlineVersion().getMetadata(); |
| 397 | + String version = metaData.getVersion().toString(); |
| 398 | + String id = metaData.getId().toString(); |
| 399 | + URL url = RemoteModuleExtension.getDownloadUrl(metaData); |
| 400 | + String fileName = String.format("%s-%s.jar", id, version); |
| 401 | + Path folder = PathManager.getInstance().getHomeModPath().normalize(); |
| 402 | + Path target = folder.resolve(fileName); |
| 403 | + urlToTargetMap.put(url, target); |
| 404 | + } |
| 405 | + return urlToTargetMap; |
| 406 | + } |
| 407 | + |
| 408 | + |
| 409 | + private static final class DependencyResolutionFailed extends Exception { |
| 410 | + DependencyResolutionFailed(String message) { |
| 411 | + super(message); |
| 412 | + } |
| 413 | + } |
| 414 | + |
| 415 | + private static class MultiFileDownloader implements Callable<List<Path>> { |
| 416 | + private LinkedHashMap<URL, Path> urlToTargetMap; |
| 417 | + private ProgressListener progressListener; |
| 418 | + |
| 419 | + public MultiFileDownloader(LinkedHashMap<URL, Path> urlToTargetMap, ProgressListener progressListener) { |
| 420 | + this.urlToTargetMap = urlToTargetMap; |
| 421 | + this.progressListener = progressListener; |
| 422 | + } |
| 423 | + |
| 424 | + @Override |
| 425 | + public List<Path> call() throws Exception { |
| 426 | + List<Path> downloadedFiles = new ArrayList<>(); |
| 427 | + float fractionPerFile = (float) 1 / urlToTargetMap.size(); |
| 428 | + int index = 0; |
| 429 | + for (Map.Entry<URL, Path> entry: urlToTargetMap.entrySet()) { |
| 430 | + float progressWithFiles= fractionPerFile*index; |
| 431 | + ProgressListener singleDownloadListener = new ProgressListener() { |
| 432 | + @Override |
| 433 | + public void onProgress(float fraction) { |
| 434 | + float totalPrecentDone = progressWithFiles + (fraction/urlToTargetMap.size()); |
| 435 | + progressListener.onProgress(totalPrecentDone); |
| 436 | + } |
| 437 | + }; |
| 438 | + FileDownloader fileDownloader = new FileDownloader(entry.getKey(), entry.getValue(), |
| 439 | + singleDownloadListener); |
| 440 | + downloadedFiles.add(fileDownloader.call()); |
| 441 | + index++; |
| 442 | + }; |
| 443 | + return downloadedFiles; |
| 444 | + } |
| 445 | + } |
| 446 | + |
| 447 | + /** |
| 448 | + * @return All modules that are required to play the online version of the specified module. The list contains the |
| 449 | + * passed module too. |
| 450 | + */ |
| 451 | + private List<ModuleSelectionInfo> getModulesRequiredFor(ModuleSelectionInfo mainModuleInfo) throws DependencyResolutionFailed { |
| 452 | + ModuleMetadata mainModuleMetadata= mainModuleInfo.getOnlineVersion().getMetadata(); |
| 453 | + LinkedList<Name> idsToCheck = Lists.newLinkedList(); |
| 454 | + idsToCheck.add(mainModuleMetadata.getId()); |
| 455 | + Map<Name, ModuleSelectionInfo> requiredIdToMetaDataMap = Maps.newLinkedHashMap(); |
| 456 | + requiredIdToMetaDataMap.put(mainModuleMetadata.getId(), mainModuleInfo); |
| 457 | + while (!idsToCheck.isEmpty()) { |
| 458 | + Name moduleToCheck = idsToCheck.removeFirst(); |
| 459 | + ModuleSelectionInfo moduleToCheckInfo =requiredIdToMetaDataMap.get(moduleToCheck); |
| 460 | + ModuleMetadata metaDataOfModuleToCheck = moduleToCheckInfo.getOnlineVersion().getMetadata(); |
| 461 | + |
| 462 | + for (DependencyInfo dependencyInfo : metaDataOfModuleToCheck.getDependencies()) { |
| 463 | + Name depName = dependencyInfo.getId(); |
| 464 | + |
| 465 | + ModuleMetadata depMetaData; |
| 466 | + if (depName.equals(ENGINE_MODULE_NAME)) { |
| 467 | + depMetaData = moduleManager.getRegistry().getLatestModuleVersion(ENGINE_MODULE_NAME).getMetadata(); |
| 468 | + if (!dependencyInfo.versionRange().contains(depMetaData.getVersion())) { |
| 469 | + throw new DependencyResolutionFailed(String.format( |
| 470 | + "Module %s %s requires %s in version range %s, but you are using version %s", |
| 471 | + moduleToCheck,metaDataOfModuleToCheck.getVersion(), depName, dependencyInfo.versionRange(), |
| 472 | + depMetaData.getVersion())); |
| 473 | + } |
| 474 | + } else { |
| 475 | + ModuleSelectionInfo depInfo = modulesLookup.get(depName); |
| 476 | + if (depInfo == null) { |
| 477 | + throw new DependencyResolutionFailed(String.format("%s requires %s which is missing", moduleToCheck, |
| 478 | + depName)); |
| 479 | + } |
| 480 | + depMetaData = depInfo.getOnlineVersion().getMetadata(); |
| 481 | + |
| 482 | + if (!dependencyInfo.versionRange().contains(depMetaData.getVersion())) { |
| 483 | + throw new DependencyResolutionFailed(String.format( |
| 484 | + "Module %s %s requires %s in version range %s, but the online version has version %s", |
| 485 | + moduleToCheck,metaDataOfModuleToCheck.getVersion(), depName, dependencyInfo.versionRange(), |
| 486 | + depMetaData.getVersion())); |
| 487 | + } |
| 488 | + if (!requiredIdToMetaDataMap.containsKey(depName)) { |
| 489 | + idsToCheck.add(depName); |
| 490 | + requiredIdToMetaDataMap.put(depName, depInfo); |
| 491 | + } |
| 492 | + } |
392 | 493 | } |
393 | | - }); |
394 | | - String fileName = String.format("%s-%s.jar", id, version); |
395 | | - Path folder = PathManager.getInstance().getHomeModPath().normalize(); |
396 | | - Path target = folder.resolve(fileName); |
| 494 | + } |
| 495 | + List<ModuleSelectionInfo> sortedDependencies = Lists.newArrayList(requiredIdToMetaDataMap.values()); |
| 496 | + return sortedDependencies; |
| 497 | + } |
397 | 498 |
|
398 | | - FileDownloader operation = new FileDownloader(url, target, progressListener); |
399 | | - popup.startOperation(operation, true); |
| 499 | + /** |
| 500 | + * |
| 501 | + * @return all modules that need to be downloaded to use the newest version of the specified module and all its |
| 502 | + * dependencies. |
| 503 | + */ |
| 504 | + private List<ModuleSelectionInfo> getModulesRequiredToDownloadFor(ModuleSelectionInfo mainModuleInfo) |
| 505 | + throws DependencyResolutionFailed { |
| 506 | + List<ModuleSelectionInfo> requiredModules = getModulesRequiredFor(mainModuleInfo); |
| 507 | + |
| 508 | + List<ModuleSelectionInfo> modulesToDownload = requiredModules.stream().filter(m -> m.isOnlineVersionNewer()) |
| 509 | + .collect(Collectors.toList()); |
| 510 | + return modulesToDownload; |
400 | 511 | } |
401 | 512 |
|
| 513 | + |
402 | 514 | private void updateValidToSelect() { |
403 | 515 | List<Name> selectedModules = Lists.newArrayList(); |
404 | 516 | for (ModuleSelectionInfo info : sortedModules) { |
@@ -429,7 +541,7 @@ private void setSelectedVersions(ResolutionResult currentSelectionResults) { |
429 | 541 |
|
430 | 542 | private void updateModuleInformation() { |
431 | 543 |
|
432 | | - Set<Name> filtered = ImmutableSet.of(new Name("engine"), new Name("engine-test")); |
| 544 | + Set<Name> filtered = ImmutableSet.of(ENGINE_MODULE_NAME, new Name("engine-test")); |
433 | 545 | for (RemoteModule remote : metaDownloader.getModules()) { |
434 | 546 | ModuleSelectionInfo info = modulesLookup.get(remote.getId()); |
435 | 547 | if (!filtered.contains(remote.getId())) { |
@@ -602,5 +714,29 @@ public boolean isValidToSelect() { |
602 | 714 | public void setValidToSelect(boolean validToSelect) { |
603 | 715 | this.validToSelect = validToSelect; |
604 | 716 | } |
| 717 | + |
| 718 | + public boolean isOnlineVersionNewer() { |
| 719 | + if (onlineVersion == null) { |
| 720 | + return false; |
| 721 | + } |
| 722 | + if (latestVersion == null) { |
| 723 | + return true; |
| 724 | + } |
| 725 | + int versionCompare = onlineVersion.getVersion().compareTo(latestVersion.getVersion()); |
| 726 | + if (versionCompare > 0) { |
| 727 | + return true; |
| 728 | + } else if (versionCompare == 0) { |
| 729 | + /* |
| 730 | + * Multiple binaries get released as the same snapshot version, A version name match thus does not |
| 731 | + * gurantee that we have the newest version already if it is a snapshot version. |
| 732 | + * |
| 733 | + * Having the user redownload the same binary again is not ideal, but it is better then ahving the user |
| 734 | + * being stuck on an outdated snapshot binary. |
| 735 | + */ |
| 736 | + return onlineVersion.getVersion().isSnapshot(); |
| 737 | + } else { |
| 738 | + return false; |
| 739 | + } |
| 740 | + } |
605 | 741 | } |
606 | 742 | } |
0 commit comments