feat(skin): WIP: download skin from CSL server.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
|
||||
public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
public OfflineAccountSkinPane(OfflineAccount account) {
|
||||
|
||||
JFXDialogLayout layout = new JFXDialogLayout();
|
||||
getChildren().setAll(layout);
|
||||
|
||||
MultiFileItem<>
|
||||
|
||||
}
|
||||
}
|
||||
@@ -51,15 +51,13 @@ public class OfflineAccount extends Account {
|
||||
private final AuthlibInjectorArtifactProvider downloader;
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final String skin;
|
||||
private final String cape;
|
||||
private final Map<TextureType, Texture> textures;
|
||||
|
||||
protected OfflineAccount(AuthlibInjectorArtifactProvider downloader, String username, UUID uuid, String skin, String cape) {
|
||||
protected OfflineAccount(AuthlibInjectorArtifactProvider downloader, String username, UUID uuid, Map<TextureType, Texture> textures) {
|
||||
this.downloader = requireNonNull(downloader);
|
||||
this.username = requireNonNull(username);
|
||||
this.uuid = requireNonNull(uuid);
|
||||
this.skin = skin;
|
||||
this.cape = cape;
|
||||
this.textures = textures;
|
||||
|
||||
if (StringUtils.isBlank(username)) {
|
||||
throw new IllegalArgumentException("Username cannot be blank");
|
||||
|
||||
202
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java
Normal file
202
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.offline;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public class Skin {
|
||||
|
||||
public enum Type {
|
||||
DEFAULT,
|
||||
STEVE,
|
||||
ALEX,
|
||||
LOCAL_FILE,
|
||||
CUSTOM_SKIN_LOADER_API,
|
||||
YGGDRASIL_API
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String value;
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Task<Texture> toTexture(String username) {
|
||||
switch (type) {
|
||||
case DEFAULT:
|
||||
return Task.supplyAsync(() -> null);
|
||||
case STEVE:
|
||||
return Task.supplyAsync(() -> Texture.loadTexture(Skin.class.getResourceAsStream("/assets/img/steve.png")));
|
||||
case ALEX:
|
||||
return Task.supplyAsync(() -> Texture.loadTexture(Skin.class.getResourceAsStream("/assets/img/alex.png")));
|
||||
case LOCAL_FILE:
|
||||
return Task.supplyAsync(() -> Texture.loadTexture(Files.newInputStream(Paths.get(value))));
|
||||
case CUSTOM_SKIN_LOADER_API:
|
||||
return Task.composeAsync(() -> new GetTask(new URL(String.format("%s/%s.json", value, username))))
|
||||
.thenComposeAsync(json -> {
|
||||
SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class);
|
||||
|
||||
if (!result.hasSkin()) {
|
||||
return Task.supplyAsync(() -> null);
|
||||
}
|
||||
|
||||
return new FetchBytesTask(new URL(String.format("%s/textures/%s", value, result.getHash())), 3);
|
||||
}).thenApplyAsync(Texture::loadTexture);
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class FetchBytesTask extends FetchTask<InputStream> {
|
||||
|
||||
public FetchBytesTask(URL url, int retry) {
|
||||
super(Collections.singletonList(url), retry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void useCachedResult(Path cachedFile) throws IOException {
|
||||
setResult(Files.newInputStream(cachedFile));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EnumCheckETag shouldCheckETag() {
|
||||
return EnumCheckETag.CHECK_E_TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext(URLConnection conn, boolean checkETag) throws IOException {
|
||||
return new Context() {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int len) {
|
||||
baos.write(buffer, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!isSuccess()) return;
|
||||
|
||||
setResult(new ByteArrayInputStream(baos.toByteArray()));
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheBytes(baos.toByteArray(), conn);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class SkinJson {
|
||||
private final String username;
|
||||
private final String skin;
|
||||
private final String cape;
|
||||
private final String elytra;
|
||||
|
||||
@SerializedName(value = "textures", alternate = { "skins" })
|
||||
private final TextureJson textures;
|
||||
|
||||
public SkinJson(String username, String skin, String cape, String elytra, TextureJson textures) {
|
||||
this.username = username;
|
||||
this.skin = skin;
|
||||
this.cape = cape;
|
||||
this.elytra = elytra;
|
||||
this.textures = textures;
|
||||
}
|
||||
|
||||
public boolean hasSkin() {
|
||||
return StringUtils.isNotBlank(username);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextureModel getModel() {
|
||||
if (textures != null && textures.slim != null) {
|
||||
return TextureModel.ALEX;
|
||||
} else if (textures != null && textures.defaultSkin != null) {
|
||||
return TextureModel.STEVE;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAlexModelHash() {
|
||||
if (textures != null && textures.slim != null) {
|
||||
return textures.slim;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getSteveModelHash() {
|
||||
if (textures != null && textures.defaultSkin != null) {
|
||||
return textures.defaultSkin;
|
||||
} else return skin;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
TextureModel model = getModel();
|
||||
if (model == TextureModel.ALEX)
|
||||
return getAlexModelHash();
|
||||
else if (model == TextureModel.STEVE)
|
||||
return getSteveModelHash();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class TextureJson {
|
||||
@SerializedName("default")
|
||||
private final String defaultSkin;
|
||||
|
||||
private final String slim;
|
||||
private final String cape;
|
||||
private final String elytra;
|
||||
|
||||
public TextureJson(String defaultSkin, String slim, String cape, String elytra) {
|
||||
this.defaultSkin = defaultSkin;
|
||||
this.slim = slim;
|
||||
this.cape = cape;
|
||||
this.elytra = elytra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.offline;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class Texture {
|
||||
private final String hash;
|
||||
private final byte[] data;
|
||||
|
||||
public Texture(String hash, byte[] data) {
|
||||
this.hash = requireNonNull(hash);
|
||||
this.data = requireNonNull(data);
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
private static final Map<String, Texture> textures = new HashMap<>();
|
||||
|
||||
public static boolean hasTexture(String hash) {
|
||||
return textures.containsKey(hash);
|
||||
}
|
||||
|
||||
public static Texture getTexture(String hash) {
|
||||
return textures.get(hash);
|
||||
}
|
||||
|
||||
private static String computeTextureHash(BufferedImage img) {
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
byte[] buf = new byte[4096];
|
||||
|
||||
putInt(buf, 0, width);
|
||||
putInt(buf, 4, height);
|
||||
int pos = 8;
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
putInt(buf, pos, img.getRGB(x, y));
|
||||
if (buf[pos + 0] == 0) {
|
||||
buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0;
|
||||
}
|
||||
pos += 4;
|
||||
if (pos == buf.length) {
|
||||
pos = 0;
|
||||
digest.update(buf, 0, buf.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos > 0) {
|
||||
digest.update(buf, 0, pos);
|
||||
}
|
||||
|
||||
byte[] sha256 = digest.digest();
|
||||
return String.format("%0" + (sha256.length << 1) + "x", new BigInteger(1, sha256));
|
||||
}
|
||||
|
||||
private static void putInt(byte[] array, int offset, int x) {
|
||||
array[offset + 0] = (byte) (x >> 24 & 0xff);
|
||||
array[offset + 1] = (byte) (x >> 16 & 0xff);
|
||||
array[offset + 2] = (byte) (x >> 8 & 0xff);
|
||||
array[offset + 3] = (byte) (x >> 0 & 0xff);
|
||||
}
|
||||
|
||||
public static Texture loadTexture(InputStream in) throws IOException {
|
||||
if (in == null) return null;
|
||||
BufferedImage img = ImageIO.read(in);
|
||||
if (img == null) {
|
||||
throw new IIOException("No image found");
|
||||
}
|
||||
|
||||
String hash = computeTextureHash(img);
|
||||
|
||||
Texture existent = textures.get(hash);
|
||||
if (existent != null) {
|
||||
return existent;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
ImageIO.write(img, "png", buf);
|
||||
Texture texture = new Texture(hash, buf.toByteArray());
|
||||
|
||||
existent = textures.putIfAbsent(hash, texture);
|
||||
|
||||
if (existent != null) {
|
||||
return existent;
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture loadTexture(String url) throws IOException {
|
||||
if (url == null) return null;
|
||||
return loadTexture(new URL(url).openStream());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,15 +28,7 @@ import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.HttpServer;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -50,7 +42,6 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class YggdrasilServer extends HttpServer {
|
||||
|
||||
private final Map<String, Texture> textures = new HashMap<>();
|
||||
private final Map<UUID, Character> charactersByUuid = new HashMap<>();
|
||||
private final Map<String, Character> charactersByName = new HashMap<>();
|
||||
|
||||
@@ -125,8 +116,8 @@ public class YggdrasilServer extends HttpServer {
|
||||
private Response texture(Request request) {
|
||||
String hash = request.getPathVariables().group("hash");
|
||||
|
||||
if (textures.containsKey(hash)) {
|
||||
Texture texture = textures.get(hash);
|
||||
if (Texture.hasTexture(hash)) {
|
||||
Texture texture = Texture.getTexture(hash);
|
||||
Response response = newFixedLengthResponse(Response.Status.OK, "image/png", texture.getInputStream(), texture.getLength());
|
||||
response.addHeader("Etag", String.format("\"%s\"", hash));
|
||||
response.addHeader("Cache-Control", "max-age=2592000, public");
|
||||
@@ -144,80 +135,6 @@ public class YggdrasilServer extends HttpServer {
|
||||
return Optional.ofNullable(charactersByName.get(uuid));
|
||||
}
|
||||
|
||||
private static String computeTextureHash(BufferedImage img) {
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
byte[] buf = new byte[4096];
|
||||
|
||||
putInt(buf, 0, width);
|
||||
putInt(buf, 4, height);
|
||||
int pos = 8;
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
putInt(buf, pos, img.getRGB(x, y));
|
||||
if (buf[pos + 0] == 0) {
|
||||
buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0;
|
||||
}
|
||||
pos += 4;
|
||||
if (pos == buf.length) {
|
||||
pos = 0;
|
||||
digest.update(buf, 0, buf.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos > 0) {
|
||||
digest.update(buf, 0, pos);
|
||||
}
|
||||
|
||||
byte[] sha256 = digest.digest();
|
||||
return String.format("%0" + (sha256.length << 1) + "x", new BigInteger(1, sha256));
|
||||
}
|
||||
|
||||
private static void putInt(byte[] array, int offset, int x) {
|
||||
array[offset + 0] = (byte) (x >> 24 & 0xff);
|
||||
array[offset + 1] = (byte) (x >> 16 & 0xff);
|
||||
array[offset + 2] = (byte) (x >> 8 & 0xff);
|
||||
array[offset + 3] = (byte) (x >> 0 & 0xff);
|
||||
}
|
||||
|
||||
private Texture loadTexture(InputStream in) throws IOException {
|
||||
if (in == null) return null;
|
||||
BufferedImage img = ImageIO.read(in);
|
||||
if (img == null) {
|
||||
throw new IIOException("No image found");
|
||||
}
|
||||
|
||||
String hash = computeTextureHash(img);
|
||||
|
||||
Texture existent = textures.get(hash);
|
||||
if (existent != null) {
|
||||
return existent;
|
||||
}
|
||||
|
||||
String url = String.format("http://localhost:%d/textures/%s", getListeningPort(), hash);
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
ImageIO.write(img, "png", buf);
|
||||
Texture texture = new Texture(hash, buf.toByteArray(), url);
|
||||
|
||||
existent = textures.putIfAbsent(hash, texture);
|
||||
|
||||
if (existent != null) {
|
||||
return existent;
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
public Texture loadTexture(String url) throws IOException {
|
||||
if (url == null) return null;
|
||||
return loadTexture(new URL(url).openStream());
|
||||
}
|
||||
|
||||
public void addCharacter(Character character) {
|
||||
charactersByUuid.put(character.getUUID(), character);
|
||||
charactersByName.put(character.getName(), character);
|
||||
@@ -267,7 +184,7 @@ public class YggdrasilServer extends HttpServer {
|
||||
Map<String, Object> realTextures = new HashMap<>();
|
||||
for (Map.Entry<TextureType, Texture> textureEntry : textures.entrySet()) {
|
||||
if (textureEntry.getValue() == null) continue;
|
||||
realTextures.put(textureEntry.getKey().name(), mapOf(pair("url", rootUrl + "/textures/" + textureEntry.getValue().hash)));
|
||||
realTextures.put(textureEntry.getKey().name(), mapOf(pair("url", rootUrl + "/textures/" + textureEntry.getValue().getHash())));
|
||||
}
|
||||
|
||||
Map<String, Object> textureResponse = mapOf(
|
||||
@@ -289,30 +206,6 @@ public class YggdrasilServer extends HttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Texture {
|
||||
private final String hash;
|
||||
private final byte[] data;
|
||||
private final String url;
|
||||
|
||||
public Texture(String hash, byte[] data, String url) {
|
||||
this.hash = requireNonNull(hash);
|
||||
this.data = requireNonNull(data);
|
||||
this.url = requireNonNull(url);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return data.length;
|
||||
}
|
||||
}
|
||||
|
||||
// === Signature ===
|
||||
|
||||
private static final KeyPair keyPair = KeyUtils.generateKey();
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.*;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -35,6 +32,7 @@ import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -190,14 +188,34 @@ public class CacheRepository {
|
||||
// conn.setRequestProperty("If-Modified-Since", eTagItem.getRemoteLastModified());
|
||||
}
|
||||
|
||||
public synchronized void cacheRemoteFile(Path downloaded, URLConnection conn) throws IOException {
|
||||
public void cacheRemoteFile(Path downloaded, URLConnection conn) throws IOException {
|
||||
cacheData(() -> {
|
||||
String hash = Hex.encodeHex(DigestUtils.digest(SHA1, downloaded));
|
||||
Path cached = cacheFile(downloaded, SHA1, hash);
|
||||
return new CacheResult(hash, cached);
|
||||
}, conn);
|
||||
}
|
||||
|
||||
public void cacheText(String text, URLConnection conn) throws IOException {
|
||||
cacheBytes(text.getBytes(UTF_8), conn);
|
||||
}
|
||||
|
||||
public void cacheBytes(byte[] bytes, URLConnection conn) throws IOException {
|
||||
cacheData(() -> {
|
||||
String hash = Hex.encodeHex(DigestUtils.digest(SHA1, bytes));
|
||||
Path cached = getFile(SHA1, hash);
|
||||
FileUtils.writeBytes(cached.toFile(), bytes);
|
||||
return new CacheResult(hash, cached);
|
||||
}, conn);
|
||||
}
|
||||
|
||||
public synchronized void cacheData(ExceptionalSupplier<CacheResult, IOException> cacheSupplier, URLConnection conn) throws IOException {
|
||||
String eTag = conn.getHeaderField("ETag");
|
||||
if (eTag == null) return;
|
||||
String url = conn.getURL().toString();
|
||||
String lastModified = conn.getHeaderField("Last-Modified");
|
||||
String hash = Hex.encodeHex(DigestUtils.digest(SHA1, downloaded));
|
||||
Path cached = cacheFile(downloaded, SHA1, hash);
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, hash, Files.getLastModifiedTime(cached).toMillis(), lastModified);
|
||||
CacheResult cacheResult = cacheSupplier.get();
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
@@ -208,22 +226,13 @@ public class CacheRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void cacheText(String text, URLConnection conn) throws IOException {
|
||||
String eTag = conn.getHeaderField("ETag");
|
||||
if (eTag == null) return;
|
||||
String url = conn.getURL().toString();
|
||||
String lastModified = conn.getHeaderField("Last-Modified");
|
||||
String hash = Hex.encodeHex(DigestUtils.digest(SHA1, text));
|
||||
Path cached = getFile(SHA1, hash);
|
||||
FileUtils.writeText(cached.toFile(), text);
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, hash, Files.getLastModifiedTime(cached).toMillis(), lastModified);
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
index.compute(eTagItem.url, updateEntity(eTagItem));
|
||||
saveETagIndex();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
private static class CacheResult {
|
||||
public String hash;
|
||||
public Path cachedFile;
|
||||
|
||||
public CacheResult(String hash, Path cachedFile) {
|
||||
this.hash = hash;
|
||||
this.cachedFile = cachedFile;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user