From 60143d52999dc06f2aad126df01c4cede8c26dd9 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Fri, 7 Sep 2018 00:35:28 +0800 Subject: [PATCH] Refactor FileDownloadTask --- .../hmcl/game/HMCLDependencyManager.java | 12 +- .../hmcl/game/HMCLGameAssetDownloadTask.java | 94 ---------- .../game/HMCLGameAssetIndexDownloadTask.java | 73 -------- .../jackhuang/hmcl/game/HMCLGameBuilder.java | 40 ----- .../hmcl/game/HMCLGameDownloadTask.java | 86 --------- .../hmcl/game/HMCLLocalRepository.java | 165 +----------------- .../org/jackhuang/hmcl/setting/Settings.java | 2 + .../hmcl/ui/construct/TaskListPane.java | 4 +- .../hmcl/download/DefaultGameBuilder.java | 2 +- .../download/game/GameAssetDownloadTask.java | 58 +++--- .../game/GameAssetIndexDownloadTask.java | 10 +- .../download/game/GameAssetRefreshTask.java | 94 ---------- .../hmcl/download/game/GameDownloadTask.java | 10 +- .../jackhuang/hmcl/task/FileDownloadTask.java | 46 +++++ .../jackhuang/hmcl/util/LocalRepository.java | 111 ++++++++++++ 15 files changed, 217 insertions(+), 590 deletions(-) delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetDownloadTask.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetIndexDownloadTask.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/LocalRepository.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java index 337401e61..033de21c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.game; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.download.GameBuilder; +import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.ParallelTask; import org.jackhuang.hmcl.task.Task; @@ -28,23 +28,15 @@ import org.jackhuang.hmcl.task.Task; * @author huangyuhui */ public class HMCLDependencyManager extends DefaultDependencyManager { - private final Profile profile; public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) { super(profile.getRepository(), downloadProvider); - - this.profile = profile; - } - - @Override - public GameBuilder gameBuilder() { - return new HMCLGameBuilder(profile); } @Override public Task checkGameCompletionAsync(Version version) { return new ParallelTask( - new HMCLGameAssetDownloadTask(this, version), + new GameAssetDownloadTask(this, version), new HMCLGameLibrariesTask(this, version) ); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetDownloadTask.java deleted file mode 100644 index 87cf5f0e1..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetDownloadTask.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game; - -import org.jackhuang.hmcl.download.AbstractDependencyManager; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.*; - -import java.io.File; -import java.nio.file.Path; -import java.util.*; - -public class HMCLGameAssetDownloadTask extends Task { - - private final AbstractDependencyManager dependencyManager; - private final Version version; - private final AssetIndexInfo assetIndexInfo; - private final File assetIndexFile; - private final List dependents = new LinkedList<>(); - private final List dependencies = new LinkedList<>(); - - /** - * Constructor. - * - * @param dependencyManager the dependency manager that can provides {@link GameRepository} - * @param version the resolved version - */ - public HMCLGameAssetDownloadTask(HMCLDependencyManager dependencyManager, Version version) { - this.dependencyManager = dependencyManager; - this.version = version; - this.assetIndexInfo = version.getAssetIndex(); - this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); - - if (!assetIndexFile.exists()) - dependents.add(new HMCLGameAssetIndexDownloadTask(dependencyManager, version)); - } - - @Override - public Collection getDependents() { - return dependents; - } - - @Override - public Collection getDependencies() { - return dependencies; - } - - @Override - public void execute() throws Exception { - AssetIndex index = Constants.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class); - int progress = 0; - if (index != null) - for (AssetObject assetObject : index.getObjects().values()) { - if (Thread.interrupted()) - throw new InterruptedException(); - - File file = dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject); - if (file.isFile()) - HMCLLocalRepository.REPOSITORY.tryCacheAssetObject(assetObject, file.toPath()); - else { - Optional path = HMCLLocalRepository.REPOSITORY.getAssetObject(assetObject); - if (path.isPresent()) { - FileUtils.copyFile(path.get().toFile(), file); - } else { - String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); - FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); - task.setName(assetObject.getHash()); - dependencies.add(task.finalized((v, succ) -> { - if (succ) - HMCLLocalRepository.REPOSITORY.cacheAssetObject(assetObject, file.toPath()); - })); - } - } - - updateProgress(++progress, index.getObjects().size()); - } - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetIndexDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetIndexDownloadTask.java deleted file mode 100644 index f42005549..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameAssetIndexDownloadTask.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game; - -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.NetworkUtils; - -import java.io.File; -import java.net.URL; -import java.nio.file.Path; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; - -public class HMCLGameAssetIndexDownloadTask extends Task { - - private final HMCLDependencyManager dependencyManager; - private final Version version; - private final List dependencies = new LinkedList<>(); - - /** - * Constructor. - * - * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository} - * @param version the resolved version - */ - public HMCLGameAssetIndexDownloadTask(HMCLDependencyManager dependencyManager, Version version) { - this.dependencyManager = dependencyManager; - this.version = version; - setSignificance(TaskSignificance.MODERATE); - } - - @Override - public List getDependencies() { - return dependencies; - } - - @Override - public void execute() throws Exception { - AssetIndexInfo assetIndexInfo = version.getAssetIndex(); - File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); - - Optional path = HMCLLocalRepository.REPOSITORY.getAssetIndex(assetIndexInfo); - if (path.isPresent()) { - FileUtils.copyFile(path.get().toFile(), assetIndexFile); - } else { - URL url = NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())); - dependencies.add(new FileDownloadTask(url, assetIndexFile) - .finalized((v, succ) -> { - if (succ) - HMCLLocalRepository.REPOSITORY.cacheAssetIndex(assetIndexInfo, assetIndexFile.toPath()); - })); - } - } - -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java deleted file mode 100644 index a8bd08abc..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game; - -import org.jackhuang.hmcl.download.DefaultGameBuilder; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.task.Task; - -/** - * @author huangyuhui - */ -public class HMCLGameBuilder extends DefaultGameBuilder { - private final Profile profile; - - public HMCLGameBuilder(Profile profile) { - super(profile.getDependency()); - - this.profile = profile; - } - - @Override - protected Task downloadGameAsync(String gameVersion, Version version) { - return new HMCLGameDownloadTask(profile, gameVersion, version); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java deleted file mode 100644 index ffd2947c4..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game; - -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.Logging; -import org.jackhuang.hmcl.util.NetworkUtils; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Path; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.logging.Level; - -/** - * @author huangyuhui - */ -public class HMCLGameDownloadTask extends Task { - private final Profile profile; - private final String gameVersion; - private final Version version; - private final List dependencies = new LinkedList<>(); - - public HMCLGameDownloadTask(Profile profile, String gameVersion, Version version) { - this.profile = profile; - this.gameVersion = gameVersion; - this.version = version; - - setSignificance(TaskSignificance.MINOR); - } - - @Override - public Collection getDependencies() { - return dependencies; - } - - @Override - public void execute() { - File jar = profile.getRepository().getVersionJar(version); - - Optional path = HMCLLocalRepository.REPOSITORY.getVersion(gameVersion, version); - if (path.isPresent()) { - try { - FileUtils.copyFile(path.get().toFile(), jar); - return; - } catch (IOException e) { - Logging.LOG.log(Level.SEVERE, "Unable to copy cached Minecraft jar from " + path.get() + " to " + jar, e); - } - } - - URL url = NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())); - - if (version.getDownloadInfo().getSha1() == null) { - // We do not know jar's hash, then we will not cache it. - dependencies.add(new FileDownloadTask(url, jar)); - } else { - dependencies.add(new FileDownloadTask(url, jar, - new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1()) - ).then(Task.of(v -> HMCLLocalRepository.REPOSITORY.cacheVersion(version, jar.toPath())))); - } - } - -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLLocalRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLLocalRepository.java index 1a2aaf64f..b0467921b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLLocalRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLLocalRepository.java @@ -31,14 +31,10 @@ import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; -public class HMCLLocalRepository { +public class HMCLLocalRepository extends LocalRepository { private final StringProperty directory = new SimpleStringProperty(); - private Path commonDir; - private Path cacheDir; private Path librariesDir; - private Path jarsDir; - private Path assetObjectsDir; private Path indexFile; private Index index = null; @@ -59,13 +55,12 @@ public class HMCLLocalRepository { this.directory.set(directory); } - private void changeDirectory(Path commonDir) { - this.commonDir = commonDir; - cacheDir = commonDir.resolve("cache"); + @Override + protected void changeDirectory(Path commonDir) { + super.changeDirectory(commonDir); + librariesDir = commonDir.resolve("libraries"); - jarsDir = commonDir.resolve("jars"); - assetObjectsDir = commonDir.resolve("assets").resolve("objects"); - indexFile = cacheDir.resolve("index.json"); + indexFile = getCacheDirectory().resolve("index.json"); try { index = Constants.GSON.fromJson(FileUtils.readText(indexFile.toFile()), Index.class); @@ -75,15 +70,6 @@ public class HMCLLocalRepository { } } - private Path getFile(String algorithm, String hash) { - return cacheDir.resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash); - } - - private boolean fileExists(String algorithm, String hash) { - if (hash == null) return false; - return Files.exists(getFile(algorithm, hash)); - } - /** * Try to cache the library given. * This library will be cached only if it is verified. @@ -187,145 +173,6 @@ public class HMCLLocalRepository { return cache; } - /** - * Get the path of cached asset index file, empty if not cached - * - * @param info the asset index info - * @return the cached path if exists, otherwise empty - */ - public synchronized Optional getAssetIndex(AssetIndexInfo info) { - String hash = info.getSha1(); - - if (fileExists(SHA1, hash)) - return Optional.of(getFile(SHA1, hash)); - - // check old common directory - Path file = commonDir.resolve("assets").resolve("indexes").resolve(info.getId() + ".json"); - if (Files.exists(file)) { - try { - if (hash != null) { - String checksum = Hex.encodeHex(DigestUtils.digest("SHA-1", file)); - if (hash.equalsIgnoreCase(checksum)) - return Optional.of(restore(file, () -> cacheAssetIndex(info, file))); - } else { - return Optional.of(file); - } - } catch (IOException e) { - // we cannot check the hashcode or unable to move file. - } - } - - return Optional.empty(); - } - - /** - * Caches the asset index file to repository. - * - * @param assetIndexInfo the asset index of - * @param path the file being cached, must be verified - * @return cached file location - * @throws IOException if failed to calculate hash code of {@code path} or copy the file to cache - */ - public synchronized Path cacheAssetIndex(AssetIndexInfo assetIndexInfo, Path path) throws IOException { - String hash = assetIndexInfo.getSha1(); - if (hash == null) - hash = Hex.encodeHex(DigestUtils.digest(SHA1, path)); - - Path cache = getFile(SHA1, hash); - FileUtils.copyFile(path.toFile(), cache.toFile()); - - return cache; - } - - public synchronized Optional getVersion(String gameVersion, Version version) { - DownloadInfo info = version.getDownloadInfo(); - String hash = info.getSha1(); - - if (fileExists(SHA1, hash)) - return Optional.of(getFile(SHA1, hash)); - - // check old common directory, but we will no longer maintain it. - Path jar = jarsDir.resolve(gameVersion + ".jar"); - if (Files.exists(jar)) { - if (hash != null) { - try { - String checksum = Hex.encodeHex(DigestUtils.digest("SHA-1", jar)); - if (!checksum.equalsIgnoreCase(hash)) { - // The file is not the one we want - return Optional.empty(); - } else { - return Optional.of(restore(jar, () -> cacheVersion(version, jar))); - } - } catch (IOException e) { - // we cannot check the hashcode. - return Optional.empty(); - } - } else { - return Optional.of(jar); - } - } - - return Optional.empty(); - } - - public synchronized Path cacheVersion(Version version, Path path) throws IOException { - if (version.getDownloadInfo().getSha1() == null) - throw new IllegalStateException(); - - Path cache = getFile(SHA1, version.getDownloadInfo().getSha1()); - FileUtils.copyFile(path.toFile(), cache.toFile()); - return cache; - } - - public synchronized Optional getAssetObject(AssetObject assetObject) { - String hash = assetObject.getHash(); - - if (fileExists(SHA1, hash)) - return Optional.of(getFile(SHA1, hash)); - - // check old common directory, but we will no longer maintain it. - Path file = assetObjectsDir.resolve(assetObject.getLocation()); - if (Files.exists(file)) { - if (hash != null) { - try { - String checksum = Hex.encodeHex(DigestUtils.digest("SHA-1", file)); - if (!checksum.equalsIgnoreCase(hash)) { - // The file is not the one we want - return Optional.empty(); - } else { - return Optional.of(restore(file, () -> cacheAssetObject(assetObject, file))); - } - } catch (IOException e) { - // we cannot check the hashcode. - return Optional.empty(); - } - } else { - return Optional.of(file); - } - } - - return Optional.empty(); - } - - public synchronized Path cacheAssetObject(AssetObject assetObject, Path path) throws IOException { - Path cache = getFile(SHA1, assetObject.getHash()); - FileUtils.copyFile(path.toFile(), cache.toFile()); - return cache; - } - - public synchronized void tryCacheAssetObject(AssetObject assetObject, Path path) throws IOException { - Path cache = getFile(SHA1, assetObject.getHash()); - if (Files.exists(cache)) return; - FileUtils.copyFile(path.toFile(), cache.toFile()); - } - - private Path restore(Path original, ExceptionalSupplier cacheSupplier) throws IOException { - Path cache = cacheSupplier.get(); - Files.delete(original); - Files.createLink(original, cache); - return cache; - } - private void saveIndex() { if (indexFile == null || index == null) return; try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index 2d8686d11..b3a18f7c6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -21,6 +21,7 @@ import javafx.scene.text.Font; import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.game.HMCLLocalRepository; +import org.jackhuang.hmcl.util.LocalRepository; import static org.jackhuang.hmcl.setting.ConfigHolder.config; @@ -47,6 +48,7 @@ public class Settings { Accounts.init(); Profiles.init(); + LocalRepository.setInstance(HMCLLocalRepository.REPOSITORY); HMCLLocalRepository.REPOSITORY.directoryProperty().bind(config().commonDirectoryProperty()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 7760d20e8..bf558cebb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -24,9 +24,9 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; +import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; -import org.jackhuang.hmcl.game.HMCLGameAssetDownloadTask; import org.jackhuang.hmcl.game.HMCLModpackExportTask; import org.jackhuang.hmcl.game.HMCLModpackInstallTask; import org.jackhuang.hmcl.mod.*; @@ -61,7 +61,7 @@ public final class TaskListPane extends StackPane { if (!task.getSignificance().shouldShow()) return; - if (task instanceof HMCLGameAssetDownloadTask) { + if (task instanceof GameAssetDownloadTask) { task.setName(i18n("assets.download_all")); } else if (task instanceof ForgeInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java index b59b444a1..4786a16d3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -80,7 +80,7 @@ public class DefaultGameBuilder extends GameBuilder { } protected Task downloadGameAsync(String gameVersion, Version version) { - return new GameDownloadTask(dependencyManager, version); + return new GameDownloadTask(dependencyManager, gameVersion, version); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java index ff58389a6..e9b1de62f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -18,18 +18,21 @@ package org.jackhuang.hmcl.download.game; import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.game.AssetIndex; +import org.jackhuang.hmcl.game.AssetIndexInfo; import org.jackhuang.hmcl.game.AssetObject; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.LocalRepository; import org.jackhuang.hmcl.util.NetworkUtils; import java.io.File; -import java.util.*; -import java.util.logging.Level; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; /** * @@ -39,7 +42,9 @@ public final class GameAssetDownloadTask extends Task { private final AbstractDependencyManager dependencyManager; private final Version version; - private final GameAssetRefreshTask refreshTask; + private final AssetIndexInfo assetIndexInfo; + private final File assetIndexFile; + private final List dependents = new LinkedList<>(); private final List dependencies = new LinkedList<>(); /** @@ -51,12 +56,16 @@ public final class GameAssetDownloadTask extends Task { public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) { this.dependencyManager = dependencyManager; this.version = version; - this.refreshTask = new GameAssetRefreshTask(dependencyManager, version); + this.assetIndexInfo = version.getAssetIndex(); + this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); + + if (!assetIndexFile.exists()) + dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version)); } @Override public Collection getDependents() { - return Collections.singleton(refreshTask); + return dependents; } @Override @@ -66,23 +75,28 @@ public final class GameAssetDownloadTask extends Task { @Override public void execute() throws Exception { - for (Map.Entry entry : refreshTask.getResult()) { - if (Thread.interrupted()) - throw new InterruptedException(); + AssetIndex index = Constants.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class); + int progress = 0; + if (index != null) + for (AssetObject assetObject : index.getObjects().values()) { + if (Thread.interrupted()) + throw new InterruptedException(); - File file = entry.getKey(); - AssetObject assetObject = entry.getValue(); - String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); - if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) { - Logging.LOG.log(Level.SEVERE, "Unable to create new file " + file + ", because parent directory cannot be created"); - continue; + File file = dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject); + if (file.isFile()) + LocalRepository.getInstance().tryCacheFile(file.toPath(), LocalRepository.SHA1, assetObject.getHash()); + else { + String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); + FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); + task.setName(assetObject.getHash()); + dependencies.add(task + .setCaching(true) + .setCandidate(LocalRepository.getInstance().getCommonDirectory() + .resolve("assets").resolve("objects").resolve(assetObject.getLocation()))); + } + + updateProgress(++progress, index.getObjects().size()); } - if (file.isDirectory() || file.isFile() && file.exists()) - continue; - FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash())); - task.setName(assetObject.getHash()); - dependencies.add(task); - } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java index d79f0fece..854e927a7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java @@ -22,11 +22,10 @@ import org.jackhuang.hmcl.game.AssetIndexInfo; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.LocalRepository; import org.jackhuang.hmcl.util.NetworkUtils; import java.io.File; -import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -61,9 +60,6 @@ public final class GameAssetIndexDownloadTask extends Task { @Override public void execute() throws Exception { AssetIndexInfo assetIndexInfo = version.getAssetIndex(); - File assetDir = dependencyManager.getGameRepository().getAssetDirectory(version.getId(), assetIndexInfo.getId()); - if (!FileUtils.makeDirectory(assetDir)) - throw new IOException("Cannot create directory: " + assetDir); File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); // We should not check the hash code of asset index file since this file is not consistent @@ -71,7 +67,9 @@ public final class GameAssetIndexDownloadTask extends Task { dependencies.add(new FileDownloadTask( NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())), assetIndexFile - )); + ).setCaching(true) + .setCandidate(LocalRepository.getInstance().getCommonDirectory() + .resolve("assets").resolve("indexes").resolve(assetIndexInfo.getId() + ".json"))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java deleted file mode 100644 index 9b825a1d3..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.game; - -import org.jackhuang.hmcl.download.AbstractDependencyManager; -import org.jackhuang.hmcl.game.AssetIndex; -import org.jackhuang.hmcl.game.AssetIndexInfo; -import org.jackhuang.hmcl.game.AssetObject; -import org.jackhuang.hmcl.game.Version; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.task.TaskResult; -import org.jackhuang.hmcl.util.Constants; -import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.Pair; - -import java.io.File; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; - -import static org.jackhuang.hmcl.util.Pair.pair; - -/** - * This task is to extract all asset objects described in asset index json. - * - * @author huangyuhui - */ -public final class GameAssetRefreshTask extends TaskResult>> { - - private final AbstractDependencyManager dependencyManager; - private final Version version; - private final AssetIndexInfo assetIndexInfo; - private final File assetIndexFile; - private final List dependents = new LinkedList<>(); - - /** - * Constructor. - * - * @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository} - * @param version the resolved version - */ - public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) { - this.dependencyManager = dependencyManager; - this.version = version; - this.assetIndexInfo = version.getAssetIndex(); - this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); - - if (!assetIndexFile.exists()) - dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version)); - } - - @Override - public List getDependents() { - return dependents; - } - - @Override - public String getId() { - return ID; - } - - @Override - public void execute() throws Exception { - AssetIndex index = Constants.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class); - List> res = new LinkedList<>(); - int progress = 0; - if (index != null) - for (AssetObject assetObject : index.getObjects().values()) { - if (Thread.interrupted()) - throw new InterruptedException(); - - res.add(pair(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject)); - updateProgress(++progress, index.getObjects().size()); - } - setResult(res); - } - - public static final String ID = "game_asset_refresh_task"; -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java index 7db56833c..df6b1e2ce 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java @@ -22,6 +22,7 @@ import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.LocalRepository; import org.jackhuang.hmcl.util.NetworkUtils; import java.io.File; @@ -34,11 +35,13 @@ import java.util.List; */ public final class GameDownloadTask extends Task { private final DefaultDependencyManager dependencyManager; + private final String gameVersion; private final Version version; private final List dependencies = new LinkedList<>(); - public GameDownloadTask(DefaultDependencyManager dependencyManager, Version version) { + public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) { this.dependencyManager = dependencyManager; + this.gameVersion = gameVersion; this.version = version; setSignificance(TaskSignificance.MODERATE); @@ -56,8 +59,9 @@ public final class GameDownloadTask extends Task { dependencies.add(new FileDownloadTask( NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), jar, - new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1()) - )); + IntegrityCheck.of(LocalRepository.SHA1, version.getDownloadInfo().getSha1())) + .setCaching(true) + .setCandidate(LocalRepository.getInstance().getCommonDirectory().resolve("jars").resolve(gameVersion + ".jar"))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index 008c44ddf..99e13ddb1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -28,7 +28,9 @@ import java.io.RandomAccessFile; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.file.Path; import java.security.MessageDigest; +import java.util.Optional; import java.util.logging.Level; import static java.util.Objects.requireNonNull; @@ -50,6 +52,11 @@ public class FileDownloadTask extends Task { this.checksum = requireNonNull(checksum); } + public static IntegrityCheck of(String algorithm, String checksum) { + if (checksum == null) return null; + else return new IntegrityCheck(algorithm, checksum); + } + public String getAlgorithm() { return algorithm; } @@ -75,6 +82,8 @@ public class FileDownloadTask extends Task { private final IntegrityCheck integrityCheck; private final int retry; private final EventManager> onFailed = new EventManager<>(); + private Path candidate; + private boolean caching; private RandomAccessFile rFile; private InputStream stream; @@ -146,9 +155,35 @@ public class FileDownloadTask extends Task { return file; } + public FileDownloadTask setCandidate(Path candidate) { + this.candidate = candidate; + return this; + } + + public FileDownloadTask setCaching(boolean caching) { + this.caching = caching; + return this; + } + @Override public void execute() throws Exception { URL currentURL = url; + + // Check cache + if (integrityCheck != null && caching) { + Optional cache = LocalRepository.getInstance() + .checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum()); + if (cache.isPresent()) { + try { + FileUtils.copyFile(cache.get().toFile(), file); + Logging.LOG.log(Level.FINER, "Successfully verified file " + file + " from " + currentURL); + return; + } catch (IOException e) { + Logging.LOG.log(Level.WARNING, "Failed to copy cache files", e); + } + } + } + Logging.LOG.log(Level.FINER, "Downloading " + currentURL + " to " + file); Exception exception = null; @@ -257,6 +292,17 @@ public class FileDownloadTask extends Task { if (exception != null) throw new IOException("Unable to download file " + currentURL + ". " + exception.getMessage(), exception); + + if (caching) { + try { + if (integrityCheck == null) + LocalRepository.getInstance().cacheFile(file.toPath(), LocalRepository.SHA1, Hex.encodeHex(DigestUtils.digest(LocalRepository.SHA1, file.toPath()))); + else + LocalRepository.getInstance().cacheFile(file.toPath(), integrityCheck.getAlgorithm(), integrityCheck.getChecksum()); + } catch (IOException e) { + Logging.LOG.log(Level.WARNING, "Failed to cache file", e); + } + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LocalRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LocalRepository.java new file mode 100644 index 000000000..4b4cab384 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LocalRepository.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public class LocalRepository { + private Path commonDirectory; + private Path cacheDirectory; + + protected void changeDirectory(Path commonDir) { + commonDirectory = commonDir; + cacheDirectory = commonDir.resolve("cache"); + } + + public Path getCommonDirectory() { + return commonDirectory; + } + + public Path getCacheDirectory() { + return cacheDirectory; + } + + protected Path getFile(String algorithm, String hash) { + return getCacheDirectory().resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash); + } + + protected boolean fileExists(String algorithm, String hash) { + if (hash == null) return false; + Path file = getFile(algorithm, hash); + if (Files.exists(file)) { + try { + return Hex.encodeHex(DigestUtils.digest(algorithm, file)).equalsIgnoreCase(hash); + } catch (IOException e) { + return false; + } + } else { + return false; + } + } + + public void tryCacheFile(Path path, String algorithm, String hash) throws IOException { + Path cache = getFile(algorithm, hash); + if (Files.exists(cache)) return; + FileUtils.copyFile(path.toFile(), cache.toFile()); + } + + public Path cacheFile(Path path, String algorithm, String hash) throws IOException { + Path cache = getFile(algorithm, hash); + FileUtils.copyFile(path.toFile(), cache.toFile()); + return cache; + } + + public Optional checkExistentFile(Path original, String algorithm, String hash) { + if (fileExists(algorithm, hash)) + return Optional.of(getFile(algorithm, hash)); + + if (original != null && Files.exists(original)) { + if (hash != null) { + try { + String checksum = Hex.encodeHex(DigestUtils.digest(algorithm, original)); + if (checksum.equalsIgnoreCase(hash)) + return Optional.of(restore(original, () -> cacheFile(original, algorithm, hash))); + } catch (IOException e) { + // we cannot check the hashcode. + } + } else { + return Optional.of(original); + } + } + + return Optional.empty(); + } + + protected Path restore(Path original, ExceptionalSupplier cacheSupplier) throws IOException { + Path cache = cacheSupplier.get(); + Files.delete(original); + Files.createLink(original, cache); + return cache; + } + + private static LocalRepository instance = new LocalRepository(); + + public static LocalRepository getInstance() { + return instance; + } + + public static void setInstance(LocalRepository instance) { + LocalRepository.instance = instance; + } + + public static final String SHA1 = "SHA-1"; +}