修复下载缓存不生效的问题 (#4167)
This commit is contained in:
@@ -49,6 +49,8 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion;
|
|||||||
import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;
|
import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
@@ -67,7 +69,6 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -96,7 +97,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
private final StackPane center;
|
private final StackPane center;
|
||||||
|
|
||||||
private final VersionList<?> versionList;
|
private final VersionList<?> versionList;
|
||||||
private CompletableFuture<?> executor;
|
private Task<?> executor;
|
||||||
|
|
||||||
private final HBox searchBar;
|
private final HBox searchBar;
|
||||||
private final StringProperty queryString = new SimpleStringProperty();
|
private final StringProperty queryString = new SimpleStringProperty();
|
||||||
@@ -308,7 +309,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
public void refresh() {
|
public void refresh() {
|
||||||
VersionList<?> currentVersionList = versionList;
|
VersionList<?> currentVersionList = versionList;
|
||||||
root.setContent(spinner, ContainerAnimations.FADE);
|
root.setContent(spinner, ContainerAnimations.FADE);
|
||||||
executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> {
|
executor = currentVersionList.refreshAsync(gameVersion).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
List<RemoteVersion> items = loadVersions();
|
List<RemoteVersion> items = loadVersions();
|
||||||
|
|
||||||
@@ -338,6 +339,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
// https://github.com/HMCL-dev/HMCL/issues/938
|
// https://github.com/HMCL-dev/HMCL/issues/938
|
||||||
System.gc();
|
System.gc();
|
||||||
});
|
});
|
||||||
|
executor.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -348,8 +350,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
@Override
|
@Override
|
||||||
public void cleanup(Map<String, Object> settings) {
|
public void cleanup(Map<String, Object> settings) {
|
||||||
settings.remove(libraryId);
|
settings.remove(libraryId);
|
||||||
if (executor != null)
|
// fixme
|
||||||
executor.cancel(true);
|
// if (executor != null)
|
||||||
|
// executor.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRefresh() {
|
private void onRefresh() {
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
|||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
@@ -43,7 +44,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|||||||
public class DefaultCacheRepository extends CacheRepository {
|
public class DefaultCacheRepository extends CacheRepository {
|
||||||
private Path librariesDir;
|
private Path librariesDir;
|
||||||
private Path indexFile;
|
private Path indexFile;
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
|
||||||
private Index index = null;
|
private Index index = null;
|
||||||
|
|
||||||
public DefaultCacheRepository() {
|
public DefaultCacheRepository() {
|
||||||
@@ -64,10 +64,13 @@ public class DefaultCacheRepository extends CacheRepository {
|
|||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
if (Files.isRegularFile(indexFile)) {
|
if (Files.isRegularFile(indexFile)) {
|
||||||
index = JsonUtils.fromNonNullJson(Files.readString(indexFile), Index.class);
|
index = JsonUtils.fromJsonFile(indexFile, Index.class);
|
||||||
}
|
if (index == null) {
|
||||||
else
|
throw new JsonParseException("Index file is empty or invalid");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
index = new Index();
|
index = new Index();
|
||||||
|
}
|
||||||
} catch (IOException | JsonParseException e) {
|
} catch (IOException | JsonParseException e) {
|
||||||
LOG.warning("Unable to read index file", e);
|
LOG.warning("Unable to read index file", e);
|
||||||
index = new Index();
|
index = new Index();
|
||||||
@@ -82,7 +85,7 @@ public class DefaultCacheRepository extends CacheRepository {
|
|||||||
* If cannot be verified, the library will not be cached.
|
* If cannot be verified, the library will not be cached.
|
||||||
*
|
*
|
||||||
* @param library the library being cached
|
* @param library the library being cached
|
||||||
* @param jar the file of library
|
* @param jar the file of library
|
||||||
*/
|
*/
|
||||||
public void tryCacheLibrary(Library library, Path jar) {
|
public void tryCacheLibrary(Library library, Path jar) {
|
||||||
lock.readLock().lock();
|
lock.readLock().lock();
|
||||||
@@ -171,8 +174,8 @@ public class DefaultCacheRepository extends CacheRepository {
|
|||||||
* Caches the library file to repository.
|
* Caches the library file to repository.
|
||||||
*
|
*
|
||||||
* @param library the library to cache
|
* @param library the library to cache
|
||||||
* @param path the file being cached, must be verified
|
* @param path the file being cached, must be verified
|
||||||
* @param forge true if this library is provided by Forge
|
* @param forge true if this library is provided by Forge
|
||||||
* @return cached file location
|
* @return cached file location
|
||||||
* @throws IOException if failed to calculate hash code of {@code path} or copy the file to cache
|
* @throws IOException if failed to calculate hash code of {@code path} or copy the file to cache
|
||||||
*/
|
*/
|
||||||
@@ -201,25 +204,29 @@ public class DefaultCacheRepository extends CacheRepository {
|
|||||||
if (indexFile == null || index == null) return;
|
if (indexFile == null || index == null) return;
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(indexFile.getParent());
|
Files.createDirectories(indexFile.getParent());
|
||||||
JsonUtils.writeToJsonFile(indexFile, index);
|
FileUtils.saveSafely(indexFile, outputStream -> {
|
||||||
|
try (var writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) {
|
||||||
|
JsonUtils.GSON.toJson(index, writer);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Unable to save index.json", e);
|
LOG.error("Unable to save index.json", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// ```json
|
||||||
* {
|
/// {
|
||||||
* "libraries": {
|
/// "libraries": {
|
||||||
* // allow a library has multiple hash code.
|
/// // allow a library has multiple hash code.
|
||||||
* [
|
/// [
|
||||||
* "name": "net.minecraftforge:forge:1.11.2-13.20.0.2345",
|
/// "name": "net.minecraftforge:forge:1.11.2-13.20.0.2345",
|
||||||
* "hash": "blablabla",
|
/// "hash": "blablabla",
|
||||||
* "type": "forge"
|
/// "type": "forge"
|
||||||
* ]
|
/// ]
|
||||||
* }
|
/// }
|
||||||
* // assets and versions will not be included in index.
|
/// }
|
||||||
* }
|
///```
|
||||||
*/
|
/// assets and versions will not be included in index.
|
||||||
private static final class Index implements Validation {
|
private static final class Index implements Validation {
|
||||||
private final Set<LibraryIndex> libraries;
|
private final Set<LibraryIndex> libraries;
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
|||||||
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||||
|
|
||||||
VersionList<?> versionList = getVersionList(libraryId);
|
VersionList<?> versionList = getVersionList(libraryId);
|
||||||
return Task.fromCompletableFuture(versionList.loadAsync(gameVersion))
|
return versionList.loadAsync(gameVersion)
|
||||||
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
|
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
|
||||||
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))))
|
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))))
|
||||||
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));
|
.withStage(String.format("hmcl.install.%s:%s", libraryId, libraryVersion));
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.download;
|
package org.jackhuang.hmcl.download;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
@@ -40,44 +41,40 @@ public class MultipleSourceVersionList extends VersionList<RemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> loadAsync() {
|
public Task<?> loadAsync() {
|
||||||
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<?> refreshAsync(String gameVersion, int sourceIndex) {
|
private Task<?> refreshAsync(String gameVersion, int sourceIndex) {
|
||||||
VersionList<?> versionList = backends[sourceIndex];
|
VersionList<?> versionList = backends[sourceIndex];
|
||||||
CompletableFuture<Void> future = versionList.refreshAsync(gameVersion)
|
return versionList.refreshAsync(gameVersion)
|
||||||
.thenRunAsync(() -> {
|
.thenComposeAsync(() -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
versions.putAll(gameVersion, versionList.getVersions(gameVersion));
|
versions.putAll(gameVersion, versionList.getVersions(gameVersion));
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (sourceIndex == backends.length - 1) {
|
||||||
|
LOG.warning("Failed to fetch versions list from all sources", e);
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
|
||||||
|
return refreshAsync(gameVersion, sourceIndex + 1);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sourceIndex == backends.length - 1) {
|
|
||||||
return future;
|
|
||||||
} else {
|
|
||||||
return future.<CompletableFuture<?>>handle((ignore, e) -> {
|
|
||||||
if (e == null) {
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
|
|
||||||
return refreshAsync(gameVersion, sourceIndex + 1);
|
|
||||||
}).thenCompose(it -> it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
versions.clear(gameVersion);
|
versions.clear(gameVersion);
|
||||||
return refreshAsync(gameVersion, 0);
|
return refreshAsync(gameVersion, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.download;
|
package org.jackhuang.hmcl.download;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The remote version list.
|
* The remote version list.
|
||||||
*
|
*
|
||||||
* @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.
|
* @param <T> The subclass of {@code RemoteVersion}, the type of RemoteVersion.
|
||||||
*
|
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public abstract class VersionList<T extends RemoteVersion> {
|
public abstract class VersionList<T extends RemoteVersion> {
|
||||||
@@ -48,6 +47,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the version list that contains the remote versions which depends on the specific game version has been loaded.
|
* True if the version list that contains the remote versions which depends on the specific game version has been loaded.
|
||||||
|
*
|
||||||
* @param gameVersion the remote version depends on
|
* @param gameVersion the remote version depends on
|
||||||
*/
|
*/
|
||||||
public boolean isLoaded(String gameVersion) {
|
public boolean isLoaded(String gameVersion) {
|
||||||
@@ -61,44 +61,36 @@ public abstract class VersionList<T extends RemoteVersion> {
|
|||||||
/**
|
/**
|
||||||
* @return the task to reload the remote version list.
|
* @return the task to reload the remote version list.
|
||||||
*/
|
*/
|
||||||
public abstract CompletableFuture<?> refreshAsync();
|
public abstract Task<?> refreshAsync();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param gameVersion the remote version depends on
|
* @param gameVersion the remote version depends on
|
||||||
* @return the task to reload the remote version list.
|
* @return the task to reload the remote version list.
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
return refreshAsync();
|
return refreshAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<?> loadAsync() {
|
public Task<?> loadAsync() {
|
||||||
return CompletableFuture.completedFuture(null)
|
return Task.composeAsync(() -> {
|
||||||
.thenComposeAsync(unused -> {
|
lock.readLock().lock();
|
||||||
lock.readLock().lock();
|
try {
|
||||||
boolean loaded;
|
return isLoaded() ? null : refreshAsync();
|
||||||
|
} finally {
|
||||||
try {
|
lock.readLock().unlock();
|
||||||
loaded = isLoaded();
|
}
|
||||||
} finally {
|
});
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<?> loadAsync(String gameVersion) {
|
public Task<?> loadAsync(String gameVersion) {
|
||||||
return CompletableFuture.completedFuture(null)
|
return Task.composeAsync(() -> {
|
||||||
.thenComposeAsync(unused -> {
|
lock.readLock().lock();
|
||||||
lock.readLock().lock();
|
try {
|
||||||
boolean loaded;
|
return isLoaded(gameVersion) ? null : refreshAsync(gameVersion);
|
||||||
|
} finally {
|
||||||
try {
|
lock.readLock().unlock();
|
||||||
loaded = isLoaded(gameVersion);
|
}
|
||||||
} finally {
|
});
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
return loaded ? CompletableFuture.completedFuture(null) : refreshAsync(gameVersion);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<T> getVersionsImpl(String gameVersion) {
|
protected Collection<T> getVersionsImpl(String gameVersion) {
|
||||||
@@ -123,7 +115,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
|||||||
/**
|
/**
|
||||||
* Get the specific remote version.
|
* Get the specific remote version.
|
||||||
*
|
*
|
||||||
* @param gameVersion the Minecraft version that remote versions belong to
|
* @param gameVersion the Minecraft version that remote versions belong to
|
||||||
* @param remoteVersion the version of the remote version.
|
* @param remoteVersion the version of the remote version.
|
||||||
* @return the specific remote version, null if it is not found.
|
* @return the specific remote version, null if it is not found.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
|||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
|
|
||||||
public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||||
|
|
||||||
@@ -42,14 +40,14 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return CompletableFuture.runAsync(wrap(() -> {
|
return Task.runAsync(() -> {
|
||||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("P7dR8mSH"))) {
|
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("P7dR8mSH"))) {
|
||||||
for (String gameVersion : modVersion.getGameVersions()) {
|
for (String gameVersion : modVersion.getGameVersions()) {
|
||||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.fabric;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -26,10 +27,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||||
|
|
||||||
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||||
@@ -45,8 +44,8 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return CompletableFuture.runAsync(wrap(() -> {
|
return Task.runAsync(() -> {
|
||||||
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
||||||
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
|||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String LOADER_META_URL = "https://meta.fabricmc.net/v2/versions/loader";
|
private static final String LOADER_META_URL = "https://meta.fabricmc.net/v2/versions/loader";
|
||||||
|
|||||||
@@ -19,25 +19,25 @@ package org.jackhuang.hmcl.download.forge;
|
|||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.Validation;
|
import org.jackhuang.hmcl.util.gson.Validation;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
@@ -58,12 +58,12 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> loadAsync() {
|
public Task<?> loadAsync() {
|
||||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +83,10 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
String lookupVersion = toLookupVersion(gameVersion);
|
String lookupVersion = toLookupVersion(gameVersion);
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(null)
|
return new GetTask(URI.create(apiRoot + "/forge/minecraft/" + lookupVersion)).thenGetJsonAsync(listTypeOf(ForgeVersion.class))
|
||||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).getJson(listTypeOf(ForgeVersion.class))))
|
|
||||||
.thenAcceptAsync(forgeVersions -> {
|
.thenAcceptAsync(forgeVersions -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.forge;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -53,8 +54,8 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return HttpRequest.GET(FORGE_LIST).getJsonAsync(ForgeVersionRoot.class)
|
return new GetTask(FORGE_LIST).thenGetJsonAsync(ForgeVersionRoot.class)
|
||||||
.thenAcceptAsync(root -> {
|
.thenAcceptAsync(root -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
|
|
||||||
@@ -95,5 +96,5 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String FORGE_LIST = "https://hmcl-dev.github.io/metadata/forge/";
|
public static final URI FORGE_LIST = URI.create("https://hmcl-dev.github.io/metadata/forge/");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.game;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
@@ -52,8 +53,8 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return HttpRequest.GET(downloadProvider.getVersionListURL()).getJsonAsync(GameRemoteVersions.class)
|
return new GetTask(URI.create(downloadProvider.getVersionListURL())).thenGetJsonAsync(GameRemoteVersions.class)
|
||||||
.thenAcceptAsync(root -> {
|
.thenAcceptAsync(root -> {
|
||||||
GameRemoteVersions unlistedVersions = null;
|
GameRemoteVersions unlistedVersions = null;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public final class VersionJsonDownloadTask extends Task<String> {
|
|||||||
this.dependencyManager = dependencyManager;
|
this.dependencyManager = dependencyManager;
|
||||||
this.gameVersionList = dependencyManager.getVersionList("game");
|
this.gameVersionList = dependencyManager.getVersionList("game");
|
||||||
|
|
||||||
dependents.add(Task.fromCompletableFuture(gameVersionList.loadAsync(gameVersion)));
|
dependents.add(gameVersionList.loadAsync(gameVersion));
|
||||||
|
|
||||||
setSignificance(TaskSignificance.MODERATE);
|
setSignificance(TaskSignificance.MODERATE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ package org.jackhuang.hmcl.download.liteloader;
|
|||||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
@@ -54,17 +54,15 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
return HttpRequest.GET(
|
return new GetTask(NetworkUtils.withQuery(downloadProvider.injectURLWithCandidates("https://bmclapi2.bangbang93.com/liteloader/list"), Map.of("mcversion", gameVersion)))
|
||||||
downloadProvider.injectURL("https://bmclapi2.bangbang93.com/liteloader/list"), Pair.pair("mcversion", gameVersion)
|
.thenGetJsonAsync(LiteLoaderBMCLVersion.class)
|
||||||
)
|
.thenAcceptAsync(v -> {
|
||||||
.getJsonAsync(LiteLoaderBMCLVersion.class)
|
|
||||||
.thenAccept(v -> {
|
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
versions.clear();
|
versions.clear();
|
||||||
|
|||||||
@@ -20,16 +20,18 @@ package org.jackhuang.hmcl.download.liteloader;
|
|||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
@@ -50,8 +52,9 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
|||||||
public static final String LITELOADER_LIST = "https://dl.liteloader.com/versions/versions.json";
|
public static final String LITELOADER_LIST = "https://dl.liteloader.com/versions/versions.json";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
|
return new GetTask(URI.create(downloadProvider.injectURL(LITELOADER_LIST)))
|
||||||
|
.thenGetJsonAsync(LiteLoaderVersionsRoot.class)
|
||||||
.thenAcceptAsync(root -> {
|
.thenAcceptAsync(root -> {
|
||||||
LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);
|
LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);
|
||||||
if (versions == null) {
|
if (versions == null) {
|
||||||
@@ -85,7 +88,7 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ package org.jackhuang.hmcl.download.neoforge;
|
|||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
import org.jackhuang.hmcl.util.gson.Validation;
|
import org.jackhuang.hmcl.util.gson.Validation;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||||
|
|
||||||
public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVersion> {
|
public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVersion> {
|
||||||
@@ -47,12 +47,12 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> loadAsync() {
|
public Task<?> loadAsync() {
|
||||||
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +65,8 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
public Task<?> refreshAsync(String gameVersion) {
|
||||||
return CompletableFuture.completedFuture((Void) null)
|
return new GetTask(URI.create(apiRoot + "/neoforge/list/" + gameVersion)).thenGetJsonAsync(listTypeOf(NeoForgeVersion.class))
|
||||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).getJson(listTypeOf(NeoForgeVersion.class))))
|
|
||||||
.thenAcceptAsync(neoForgeVersions -> {
|
.thenAcceptAsync(neoForgeVersions -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package org.jackhuang.hmcl.download.neoforge;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {
|
public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {
|
||||||
@@ -37,17 +37,17 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return CompletableFuture.supplyAsync(wrap(() -> new OfficialAPIResult[]{
|
return Task.allOf(
|
||||||
HttpRequest.GET(downloadProvider.injectURL(OLD_URL)).getJson(OfficialAPIResult.class),
|
new GetTask(URI.create(downloadProvider.injectURL(OLD_URL))).thenGetJsonAsync(OfficialAPIResult.class),
|
||||||
HttpRequest.GET(downloadProvider.injectURL(META_URL)).getJson(OfficialAPIResult.class)
|
new GetTask(URI.create(downloadProvider.injectURL(META_URL))).thenGetJsonAsync(OfficialAPIResult.class)
|
||||||
})).thenAccept(results -> {
|
).thenAcceptAsync(results -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
versions.clear();
|
versions.clear();
|
||||||
|
|
||||||
for (String version : results[0].versions) {
|
for (String version : results.get(0).versions) {
|
||||||
versions.put("1.20.1", new NeoForgeRemoteVersion(
|
versions.put("1.20.1", new NeoForgeRemoteVersion(
|
||||||
"1.20.1", NeoForgeRemoteVersion.normalize(version),
|
"1.20.1", NeoForgeRemoteVersion.normalize(version),
|
||||||
Collections.singletonList(
|
Collections.singletonList(
|
||||||
@@ -56,7 +56,7 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String version : results[1].versions) {
|
for (String version : results.get(1).versions) {
|
||||||
String mcVersion;
|
String mcVersion;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.download.optifine;
|
|||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||||
|
|
||||||
@@ -71,8 +72,8 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return HttpRequest.GET(apiRoot + "/optifine/versionlist").getJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
return new GetTask(URI.create(apiRoot + "/optifine/versionlist")).thenGetJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
|||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
|
|
||||||
public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
||||||
|
|
||||||
@@ -42,14 +40,14 @@ public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return CompletableFuture.runAsync(wrap(() -> {
|
return Task.runAsync(() -> {
|
||||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("qsl"))) {
|
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("qsl"))) {
|
||||||
for (String gameVersion : modVersion.getGameVersions()) {
|
for (String gameVersion : modVersion.getGameVersions()) {
|
||||||
versions.put(gameVersion, new QuiltAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
versions.put(gameVersion, new QuiltAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.quilt;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.VersionList;
|
import org.jackhuang.hmcl.download.VersionList;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -26,10 +27,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||||
|
|
||||||
public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
||||||
@@ -45,8 +44,8 @@ public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<?> refreshAsync() {
|
public Task<?> refreshAsync() {
|
||||||
return CompletableFuture.runAsync(wrap(() -> {
|
return Task.runAsync(() -> {
|
||||||
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
||||||
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
|||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String LOADER_META_URL = "https://meta.quiltmc.org/v3/versions/loader";
|
private static final String LOADER_META_URL = "https://meta.quiltmc.org/v3/versions/loader";
|
||||||
|
|||||||
@@ -84,13 +84,19 @@ public abstract class FetchTask<T> extends Task<T> {
|
|||||||
URI failedURI = null;
|
URI failedURI = null;
|
||||||
boolean checkETag;
|
boolean checkETag;
|
||||||
switch (shouldCheckETag()) {
|
switch (shouldCheckETag()) {
|
||||||
case CHECK_E_TAG: checkETag = true; break;
|
case CHECK_E_TAG:
|
||||||
case NOT_CHECK_E_TAG: checkETag = false; break;
|
checkETag = true;
|
||||||
default: return;
|
break;
|
||||||
|
case NOT_CHECK_E_TAG:
|
||||||
|
checkETag = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int repeat = 0;
|
int repeat = 0;
|
||||||
download: for (URI uri : uris) {
|
download:
|
||||||
|
for (URI uri : uris) {
|
||||||
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
break download;
|
break download;
|
||||||
@@ -116,9 +122,10 @@ public abstract class FetchTask<T> extends Task<T> {
|
|||||||
try {
|
try {
|
||||||
Path cache = repository.getCachedRemoteFile(conn.getURL().toURI());
|
Path cache = repository.getCachedRemoteFile(conn.getURL().toURI());
|
||||||
useCachedResult(cache);
|
useCachedResult(cache);
|
||||||
|
LOG.info("Using cached file for " + NetworkUtils.dropQuery(uri));
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warning("Unable to use cached file, redownload " + uri, e);
|
LOG.warning("Unable to use cached file, redownload " + NetworkUtils.dropQuery(uri), e);
|
||||||
repository.removeRemoteEntry(conn.getURL().toURI());
|
repository.removeRemoteEntry(conn.getURL().toURI());
|
||||||
// Now we must reconnect the server since 304 may result in empty content,
|
// Now we must reconnect the server since 304 may result in empty content,
|
||||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task;
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -29,7 +32,6 @@ import java.util.List;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class GetTask extends FetchTask<String> {
|
public final class GetTask extends FetchTask<String> {
|
||||||
@@ -95,4 +97,11 @@ public final class GetTask extends FetchTask<String> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> Task<T> thenGetJsonAsync(Class<T> type) {
|
||||||
|
return thenGetJsonAsync(TypeToken.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Task<T> thenGetJsonAsync(TypeToken<T> type) {
|
||||||
|
return thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -917,7 +917,8 @@ public abstract class Task<T> {
|
|||||||
* @param tasks the Tasks
|
* @param tasks the Tasks
|
||||||
* @return a new Task that is completed when all of the given Tasks complete
|
* @return a new Task that is completed when all of the given Tasks complete
|
||||||
*/
|
*/
|
||||||
public static Task<List<Object>> allOf(Task<?>... tasks) {
|
@SafeVarargs
|
||||||
|
public static <T> Task<List<T>> allOf(Task<? extends T>... tasks) {
|
||||||
return allOf(Arrays.asList(tasks));
|
return allOf(Arrays.asList(tasks));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -932,8 +933,8 @@ public abstract class Task<T> {
|
|||||||
* @param tasks the Tasks
|
* @param tasks the Tasks
|
||||||
* @return a new Task that is completed when all of the given Tasks complete
|
* @return a new Task that is completed when all of the given Tasks complete
|
||||||
*/
|
*/
|
||||||
public static Task<List<Object>> allOf(Collection<Task<?>> tasks) {
|
public static <T> Task<List<T>> allOf(Collection<? extends Task<? extends T>> tasks) {
|
||||||
return new Task<List<Object>>() {
|
return new Task<>() {
|
||||||
{
|
{
|
||||||
setSignificance(TaskSignificance.MINOR);
|
setSignificance(TaskSignificance.MINOR);
|
||||||
}
|
}
|
||||||
@@ -944,7 +945,7 @@ public abstract class Task<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Task<?>> getDependents() {
|
public Collection<? extends Task<?>> getDependents() {
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,45 +18,42 @@
|
|||||||
package org.jackhuang.hmcl.util;
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.fromMaybeMalformedJson;
|
import static org.jackhuang.hmcl.util.gson.JsonUtils.*;
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson;
|
|
||||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
public class CacheRepository {
|
public class CacheRepository {
|
||||||
private Path commonDirectory;
|
private Path commonDirectory;
|
||||||
private Path cacheDirectory;
|
private Path cacheDirectory;
|
||||||
private Path indexFile;
|
private Path indexFile;
|
||||||
private Map<String, ETagItem> index;
|
private FileTime indexFileLastModified;
|
||||||
private final Map<String, Storage> storages = new HashMap<>();
|
private LinkedHashMap<URI, ETagItem> index;
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
protected final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
public void changeDirectory(Path commonDir) {
|
public void changeDirectory(Path commonDir) {
|
||||||
commonDirectory = commonDir;
|
commonDirectory = commonDir;
|
||||||
@@ -65,25 +62,25 @@ public class CacheRepository {
|
|||||||
|
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
for (Storage storage : storages.values()) {
|
|
||||||
storage.changeDirectory(cacheDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Files.isRegularFile(indexFile)) {
|
if (Files.isRegularFile(indexFile)) {
|
||||||
ETagIndex raw = JsonUtils.fromJsonFile(indexFile, ETagIndex.class);
|
try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ);
|
||||||
if (raw == null)
|
@SuppressWarnings("unused") FileLock lock = channel.tryLock(0, Long.MAX_VALUE, true)) {
|
||||||
index = new HashMap<>();
|
FileTime lastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));
|
||||||
else
|
ETagIndex raw = JsonUtils.GSON.fromJson(new BufferedReader(Channels.newReader(channel, UTF_8)), ETagIndex.class);
|
||||||
index = joinETagIndexes(raw.eTag);
|
index = raw != null ? joinETagIndexes(raw.eTag) : new LinkedHashMap<>();
|
||||||
} else
|
indexFileLastModified = lastModified;
|
||||||
index = new HashMap<>();
|
}
|
||||||
|
} else {
|
||||||
|
index = new LinkedHashMap<>();
|
||||||
|
indexFileLastModified = null;
|
||||||
|
}
|
||||||
} catch (IOException | JsonParseException e) {
|
} catch (IOException | JsonParseException e) {
|
||||||
LOG.warning("Unable to read index file", e);
|
LOG.warning("Unable to read index file", e);
|
||||||
index = new HashMap<>();
|
index = new LinkedHashMap<>();
|
||||||
|
indexFileLastModified = null;
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getCommonDirectory() {
|
public Path getCommonDirectory() {
|
||||||
@@ -94,15 +91,6 @@ public class CacheRepository {
|
|||||||
return cacheDirectory;
|
return cacheDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Storage getStorage(String key) {
|
|
||||||
lock.readLock().lock();
|
|
||||||
try {
|
|
||||||
return storages.computeIfAbsent(key, Storage::new);
|
|
||||||
} finally {
|
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Path getFile(String algorithm, String hash) {
|
protected Path getFile(String algorithm, String hash) {
|
||||||
return getCacheDirectory().resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash);
|
return getCacheDirectory().resolve(algorithm).resolve(hash.substring(0, 2)).resolve(hash);
|
||||||
}
|
}
|
||||||
@@ -165,7 +153,7 @@ public class CacheRepository {
|
|||||||
lock.readLock().lock();
|
lock.readLock().lock();
|
||||||
ETagItem eTagItem;
|
ETagItem eTagItem;
|
||||||
try {
|
try {
|
||||||
eTagItem = index.get(uri.toString());
|
eTagItem = index.get(NetworkUtils.dropQuery(uri));
|
||||||
} finally {
|
} finally {
|
||||||
lock.readLock().unlock();
|
lock.readLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -181,20 +169,27 @@ public class CacheRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeRemoteEntry(URI uri) {
|
public void removeRemoteEntry(URI uri) {
|
||||||
lock.readLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
index.remove(uri.toString());
|
index.remove(NetworkUtils.dropQuery(uri));
|
||||||
} finally {
|
} finally {
|
||||||
lock.readLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void injectConnection(URLConnection conn) {
|
public void injectConnection(URLConnection conn) {
|
||||||
String url = conn.getURL().toString();
|
conn.setUseCaches(true);
|
||||||
lock.readLock().lock();
|
|
||||||
ETagItem eTagItem;
|
URI uri;
|
||||||
try {
|
try {
|
||||||
eTagItem = index.get(url);
|
uri = NetworkUtils.dropQuery(conn.getURL().toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ETagItem eTagItem;
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
eTagItem = index.get(uri);
|
||||||
} finally {
|
} finally {
|
||||||
lock.readLock().unlock();
|
lock.readLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -228,22 +223,26 @@ public class CacheRepository {
|
|||||||
|
|
||||||
private void cacheData(URLConnection connection, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {
|
private void cacheData(URLConnection connection, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {
|
||||||
String eTag = connection.getHeaderField("ETag");
|
String eTag = connection.getHeaderField("ETag");
|
||||||
if (eTag == null || eTag.isEmpty()) return;
|
if (StringUtils.isBlank(eTag)) return;
|
||||||
String uri = connection.getURL().toString();
|
URI uri;
|
||||||
|
try {
|
||||||
|
uri = NetworkUtils.dropQuery(connection.getURL().toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
String lastModified = connection.getHeaderField("Last-Modified");
|
String lastModified = connection.getHeaderField("Last-Modified");
|
||||||
CacheResult cacheResult = cacheSupplier.get();
|
CacheResult cacheResult = cacheSupplier.get();
|
||||||
ETagItem eTagItem = new ETagItem(uri, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
ETagItem eTagItem = new ETagItem(uri, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||||
Lock writeLock = lock.writeLock();
|
lock.writeLock().lock();
|
||||||
writeLock.lock();
|
|
||||||
try {
|
try {
|
||||||
index.compute(eTagItem.url, updateEntity(eTagItem));
|
index.compute(eTagItem.url, updateEntity(eTagItem, true));
|
||||||
saveETagIndex();
|
saveETagIndex();
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CacheResult {
|
private static final class CacheResult {
|
||||||
public String hash;
|
public String hash;
|
||||||
public Path cachedFile;
|
public Path cachedFile;
|
||||||
|
|
||||||
@@ -253,11 +252,11 @@ public class CacheRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiFunction<String, ETagItem, ETagItem> updateEntity(ETagItem newItem) {
|
private BiFunction<URI, ETagItem, ETagItem> updateEntity(ETagItem newItem, boolean force) {
|
||||||
return (key, oldItem) -> {
|
return (key, oldItem) -> {
|
||||||
if (oldItem == null) {
|
if (oldItem == null) {
|
||||||
return newItem;
|
return newItem;
|
||||||
} else if (oldItem.compareTo(newItem) < 0) {
|
} else if (force || oldItem.compareTo(newItem) < 0) {
|
||||||
Path cached = getFile(SHA1, oldItem.hash);
|
Path cached = getFile(SHA1, oldItem.hash);
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(cached);
|
Files.deleteIfExists(cached);
|
||||||
@@ -272,36 +271,44 @@ public class CacheRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
private final Map<String, ETagItem> joinETagIndexes(Collection<ETagItem>... indexes) {
|
private LinkedHashMap<URI, ETagItem> joinETagIndexes(Collection<ETagItem>... indexes) {
|
||||||
Map<String, ETagItem> eTags = new ConcurrentHashMap<>();
|
var eTags = new LinkedHashMap<URI, ETagItem>();
|
||||||
|
for (Collection<ETagItem> eTagItems : indexes) {
|
||||||
Stream<ETagItem> stream = Arrays.stream(indexes).filter(Objects::nonNull).map(Collection::stream)
|
if (eTagItems != null) {
|
||||||
.reduce(Stream.empty(), Stream::concat);
|
for (ETagItem eTag : eTagItems) {
|
||||||
|
eTags.compute(eTag.url, updateEntity(eTag, false));
|
||||||
stream.forEach(eTag -> {
|
}
|
||||||
eTags.compute(eTag.url, updateEntity(eTag));
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return eTags;
|
return eTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveETagIndex() throws IOException {
|
public void saveETagIndex() throws IOException {
|
||||||
try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
|
try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||||
FileLock lock = channel.lock();
|
@SuppressWarnings("unused") FileLock lock = channel.lock()) {
|
||||||
try {
|
FileTime lastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));
|
||||||
ETagIndex indexOnDisk = fromMaybeMalformedJson(new String(Channels.newInputStream(channel).readAllBytes(), UTF_8), ETagIndex.class);
|
if (indexFileLastModified == null || lastModified == null || indexFileLastModified.compareTo(lastModified) < 0) {
|
||||||
Map<String, ETagItem> newIndex = joinETagIndexes(indexOnDisk == null ? null : indexOnDisk.eTag, index.values());
|
try {
|
||||||
channel.truncate(0);
|
ETagIndex indexOnDisk = GSON.fromJson(
|
||||||
ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8));
|
// Should not be closed
|
||||||
while (writeTo.hasRemaining()) {
|
new BufferedReader(Channels.newReader(channel, UTF_8)),
|
||||||
if (channel.write(writeTo) == 0) {
|
ETagIndex.class
|
||||||
throw new IOException("No value is written");
|
);
|
||||||
|
if (indexOnDisk != null) {
|
||||||
|
index = joinETagIndexes(index.values(), indexOnDisk.eTag);
|
||||||
|
indexFileLastModified = lastModified;
|
||||||
}
|
}
|
||||||
|
} catch (JsonSyntaxException ignored) {
|
||||||
}
|
}
|
||||||
this.index = newIndex;
|
|
||||||
} finally {
|
|
||||||
lock.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel.truncate(0);
|
||||||
|
BufferedWriter writer = new BufferedWriter(Channels.newWriter(channel, UTF_8));
|
||||||
|
JsonUtils.GSON.toJson(new ETagIndex(index.values()), writer);
|
||||||
|
writer.flush();
|
||||||
|
channel.force(true);
|
||||||
|
|
||||||
|
this.indexFileLastModified = Lang.ignoringException(() -> Files.getLastModifiedTime(indexFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +325,7 @@ public class CacheRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class ETagItem {
|
private static final class ETagItem {
|
||||||
private final String url;
|
private final URI url;
|
||||||
private final String eTag;
|
private final String eTag;
|
||||||
private final String hash;
|
private final String hash;
|
||||||
@SerializedName("local")
|
@SerializedName("local")
|
||||||
@@ -333,7 +340,7 @@ public class CacheRepository {
|
|||||||
this(null, null, null, 0, null);
|
this(null, null, null, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ETagItem(String url, String eTag, String hash, long localLastModified, String remoteLastModified) {
|
public ETagItem(URI url, String eTag, String hash, long localLastModified, String remoteLastModified) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.eTag = eTag;
|
this.eTag = eTag;
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
@@ -348,8 +355,8 @@ public class CacheRepository {
|
|||||||
ZonedDateTime thisTime = Lang.ignoringException(() -> ZonedDateTime.parse(remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);
|
ZonedDateTime thisTime = Lang.ignoringException(() -> ZonedDateTime.parse(remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);
|
||||||
ZonedDateTime otherTime = Lang.ignoringException(() -> ZonedDateTime.parse(other.remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);
|
ZonedDateTime otherTime = Lang.ignoringException(() -> ZonedDateTime.parse(other.remoteLastModified, DateTimeFormatter.RFC_1123_DATE_TIME), null);
|
||||||
if (thisTime == null && otherTime == null) return 0;
|
if (thisTime == null && otherTime == null) return 0;
|
||||||
else if (thisTime == null) return -1;
|
else if (thisTime == null) return 1;
|
||||||
else if (otherTime == null) return 1;
|
else if (otherTime == null) return -1;
|
||||||
else return thisTime.compareTo(otherTime);
|
else return thisTime.compareTo(otherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,80 +376,16 @@ public class CacheRepository {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(url, eTag, hash, localLastModified, remoteLastModified);
|
return Objects.hash(url, eTag, hash, localLastModified, remoteLastModified);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Universal cache
|
public String toString() {
|
||||||
*/
|
return "ETagItem[" +
|
||||||
public static final class Storage {
|
"url='" + url + '\'' +
|
||||||
private final String name;
|
", eTag='" + eTag + '\'' +
|
||||||
private Map<String, Object> storage;
|
", hash='" + hash + '\'' +
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
", localLastModified=" + localLastModified +
|
||||||
private Path indexFile;
|
", remoteLastModified='" + remoteLastModified + '\'' +
|
||||||
|
']';
|
||||||
public Storage(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getEntry(String key) {
|
|
||||||
lock.readLock().lock();
|
|
||||||
try {
|
|
||||||
return storage.get(key);
|
|
||||||
} finally {
|
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putEntry(String key, Object value) {
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
storage.put(key, value);
|
|
||||||
saveToFile();
|
|
||||||
} finally {
|
|
||||||
lock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void joinEntries(Map<String, Object> storage) {
|
|
||||||
this.storage.putAll(storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeDirectory(Path cacheDirectory) {
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
indexFile = cacheDirectory.resolve(name + ".json");
|
|
||||||
if (Files.isRegularFile(indexFile)) {
|
|
||||||
joinEntries(fromNonNullJson(Files.readString(indexFile), mapTypeOf(String.class, Object.class)));
|
|
||||||
}
|
|
||||||
} catch (IOException | JsonParseException e) {
|
|
||||||
LOG.warning("Unable to read storage {" + name + "} file");
|
|
||||||
} finally {
|
|
||||||
lock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveToFile() {
|
|
||||||
try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
|
|
||||||
FileLock lock = channel.lock();
|
|
||||||
try {
|
|
||||||
Map<String, Object> indexOnDisk = fromMaybeMalformedJson(new String(Channels.newInputStream(channel).readAllBytes(), UTF_8), mapTypeOf(String.class, Object.class));
|
|
||||||
if (indexOnDisk == null) indexOnDisk = new HashMap<>();
|
|
||||||
indexOnDisk.putAll(storage);
|
|
||||||
channel.truncate(0);
|
|
||||||
|
|
||||||
ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(storage).getBytes(UTF_8));
|
|
||||||
while (writeTo.hasRemaining()) {
|
|
||||||
if (channel.write(writeTo) == 0) {
|
|
||||||
throw new IOException("No value is written");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.storage = indexOnDisk;
|
|
||||||
} finally {
|
|
||||||
lock.release();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warning("Unable to write storage {" + name + "} file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.util.*;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
@@ -68,6 +69,10 @@ public final class NetworkUtils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<URI> withQuery(List<URI> list, Map<String, String> params) {
|
||||||
|
return list.stream().map(uri -> URI.create(withQuery(uri.toString(), params))).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Pair<String, String>> parseQuery(URI uri) {
|
public static List<Pair<String, String>> parseQuery(URI uri) {
|
||||||
return parseQuery(uri.getRawQuery());
|
return parseQuery(uri.getRawQuery());
|
||||||
}
|
}
|
||||||
@@ -93,9 +98,20 @@ public final class NetworkUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static URI dropQuery(URI u) {
|
||||||
|
if (u.getRawQuery() == null && u.getRawFragment() == null) {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URI(u.getScheme(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), null, null);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new AssertionError("Unreachable", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static URLConnection createConnection(URI uri) throws IOException {
|
public static URLConnection createConnection(URI uri) throws IOException {
|
||||||
URLConnection connection = uri.toURL().openConnection();
|
URLConnection connection = uri.toURL().openConnection();
|
||||||
connection.setUseCaches(false);
|
|
||||||
connection.setConnectTimeout(TIME_OUT);
|
connection.setConnectTimeout(TIME_OUT);
|
||||||
connection.setReadTimeout(TIME_OUT);
|
connection.setReadTimeout(TIME_OUT);
|
||||||
connection.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
connection.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||||
@@ -152,9 +168,10 @@ public final class NetworkUtils {
|
|||||||
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
||||||
*/
|
*/
|
||||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn, List<String> redirects) throws IOException {
|
public static HttpURLConnection resolveConnection(HttpURLConnection conn, List<String> redirects) throws IOException {
|
||||||
|
final boolean useCache = conn.getUseCaches();
|
||||||
int redirect = 0;
|
int redirect = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
conn.setUseCaches(false);
|
conn.setUseCaches(useCache);
|
||||||
conn.setConnectTimeout(TIME_OUT);
|
conn.setConnectTimeout(TIME_OUT);
|
||||||
conn.setReadTimeout(TIME_OUT);
|
conn.setReadTimeout(TIME_OUT);
|
||||||
conn.setInstanceFollowRedirects(false);
|
conn.setInstanceFollowRedirects(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user