diff --git a/src/main/java/lain/lib/Retries.java b/src/main/java/lain/lib/Retries.java new file mode 100644 index 0000000..ceebcd8 --- /dev/null +++ b/src/main/java/lain/lib/Retries.java @@ -0,0 +1,408 @@ +package lain.lib; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public final class Retries +{ + + @FunctionalInterface + public interface ThrowingAccept + { + + void accept(T t) throws Throwable; + + default Consumer toConsumer() + { + return toConsumer(Retries::rethrow); + } + + default Consumer toConsumer(Consumer handler) + { + return t -> { + try + { + accept(t); + } + catch (Throwable throwable) + { + if (handler != null) + handler.accept(throwable); + } + }; + } + + } + + @FunctionalInterface + public interface ThrowingApply + { + + R apply(T t) throws Throwable; + + default Function toFunction() + { + return toFunction(Retries::rethrow); + } + + default Function toFunction(Consumer handler) + { + return t -> { + try + { + return apply(t); + } + catch (Throwable throwable) + { + if (handler != null) + handler.accept(throwable); + return null; + } + }; + } + + } + + @FunctionalInterface + public interface ThrowingGet + { + + T get() throws Throwable; + + default Supplier toSupplier() + { + return toSupplier(Retries::rethrow); + } + + default Supplier toSupplier(Consumer handler) + { + return () -> { + try + { + return get(); + } + catch (Throwable throwable) + { + if (handler != null) + handler.accept(throwable); + return null; + } + }; + } + + } + + @FunctionalInterface + public interface ThrowingRun + { + + void run() throws Throwable; + + default Runnable toRunnable() + { + return toRunnable(Retries::rethrow); + } + + default Runnable toRunnable(Consumer handler) + { + return () -> { + try + { + run(); + } + catch (Throwable throwable) + { + if (handler != null) + handler.accept(throwable); + } + }; + } + + } + + @FunctionalInterface + public interface ThrowingTest + { + + boolean test(T t) throws Throwable; + + default Predicate toPredicate() + { + return toPredicate(Retries::rethrow); + } + + default Predicate toPredicate(Consumer handler) + { + return t -> { + try + { + return test(t); + } + catch (Throwable throwable) + { + if (handler != null) + handler.accept(throwable); + return false; + } + }; + } + + } + + public static ThrowingAccept fallback(ThrowingAccept action, ThrowingAccept other) + { + return t -> { + try + { + action.accept(t); + } + catch (Throwable throwable) + { + try + { + other.accept(t); + } + catch (Throwable otherThrowable) + { + throwable.addSuppressed(otherThrowable); + throw throwable; + } + } + }; + } + + public static ThrowingApply fallback(ThrowingApply action, ThrowingApply other) + { + return t -> { + try + { + return action.apply(t); + } + catch (Throwable throwable) + { + try + { + return other.apply(t); + } + catch (Throwable otherThrowable) + { + throwable.addSuppressed(otherThrowable); + throw throwable; + } + } + }; + } + + public static ThrowingGet fallback(ThrowingGet action, ThrowingGet other) + { + return () -> { + try + { + return action.get(); + } + catch (Throwable throwable) + { + try + { + return other.get(); + } + catch (Throwable otherThrowable) + { + throwable.addSuppressed(otherThrowable); + throw throwable; + } + } + }; + } + + public static ThrowingRun fallback(ThrowingRun action, ThrowingRun other) + { + return () -> { + try + { + action.run(); + } + catch (Throwable throwable) + { + try + { + other.run(); + } + catch (Throwable otherThrowable) + { + throwable.addSuppressed(otherThrowable); + throw throwable; + } + } + }; + } + + public static ThrowingTest fallback(ThrowingTest action, ThrowingTest other) + { + return t -> { + try + { + return action.test(t); + } + catch (Throwable throwable) + { + try + { + return other.test(t); + } + catch (Throwable otherThrowable) + { + throwable.addSuppressed(otherThrowable); + throw throwable; + } + } + }; + } + + public static T rethrow(Throwable throwable) + { + return rethrow0(throwable); + } + + @SuppressWarnings("unchecked") + private static R rethrow0(Throwable throwable) throws T + { + throw (T) throwable; + } + + public static ThrowingAccept retrying(ThrowingAccept action, Predicate shouldRetry, Consumer beforeRetry, int maxRetries) + { + AtomicReference thrown = new AtomicReference<>(); + AtomicInteger retries = new AtomicInteger(); + return t -> { + while (true) + { + try + { + if (thrown.get() != null && beforeRetry != null) + beforeRetry.accept(retries.get()); + action.accept(t); + return; + } + catch (Throwable throwable) + { + if (!thrown.compareAndSet(null, throwable)) + thrown.get().addSuppressed(throwable); + if ((shouldRetry != null && !shouldRetry.test(throwable)) || retries.getAndIncrement() == maxRetries) + break; + } + } + throw thrown.get(); + }; + } + + public static ThrowingApply retrying(ThrowingApply action, Predicate shouldRetry, Consumer beforeRetry, int maxRetries) + { + AtomicReference thrown = new AtomicReference<>(); + AtomicInteger retries = new AtomicInteger(); + return t -> { + while (true) + { + try + { + if (thrown.get() != null && beforeRetry != null) + beforeRetry.accept(retries.get()); + return action.apply(t); + } + catch (Throwable throwable) + { + if (!thrown.compareAndSet(null, throwable)) + thrown.get().addSuppressed(throwable); + if ((shouldRetry != null && !shouldRetry.test(throwable)) || retries.getAndIncrement() == maxRetries) + break; + } + } + throw thrown.get(); + }; + } + + public static ThrowingGet retrying(ThrowingGet action, Predicate shouldRetry, Consumer beforeRetry, int maxRetries) + { + AtomicReference thrown = new AtomicReference<>(); + AtomicInteger retries = new AtomicInteger(); + return () -> { + while (true) + { + try + { + if (thrown.get() != null && beforeRetry != null) + beforeRetry.accept(retries.get()); + return action.get(); + } + catch (Throwable throwable) + { + if (!thrown.compareAndSet(null, throwable)) + thrown.get().addSuppressed(throwable); + if ((shouldRetry != null && !shouldRetry.test(throwable)) || retries.getAndIncrement() == maxRetries) + break; + } + } + throw thrown.get(); + }; + } + + public static ThrowingRun retrying(ThrowingRun action, Predicate shouldRetry, Consumer beforeRetry, int maxRetries) + { + AtomicReference thrown = new AtomicReference<>(); + AtomicInteger retries = new AtomicInteger(); + return () -> { + while (true) + { + try + { + if (thrown.get() != null && beforeRetry != null) + beforeRetry.accept(retries.get()); + action.run(); + return; + } + catch (Throwable throwable) + { + if (!thrown.compareAndSet(null, throwable)) + thrown.get().addSuppressed(throwable); + if ((shouldRetry != null && !shouldRetry.test(throwable)) || retries.getAndIncrement() == maxRetries) + break; + } + } + throw thrown.get(); + }; + } + + public static ThrowingTest retrying(ThrowingTest action, Predicate shouldRetry, Consumer beforeRetry, int maxRetries) + { + AtomicReference thrown = new AtomicReference<>(); + AtomicInteger retries = new AtomicInteger(); + return t -> { + while (true) + { + try + { + if (thrown.get() != null && beforeRetry != null) + beforeRetry.accept(retries.get()); + return action.test(t); + } + catch (Throwable throwable) + { + if (!thrown.compareAndSet(null, throwable)) + thrown.get().addSuppressed(throwable); + if ((shouldRetry != null && !shouldRetry.test(throwable)) || retries.getAndIncrement() == maxRetries) + break; + } + } + throw thrown.get(); + }; + } + + private Retries() + { + } + +} diff --git a/src/main/java/lain/lib/SimpleDownloader.java b/src/main/java/lain/lib/SimpleDownloader.java new file mode 100644 index 0000000..74b54c1 --- /dev/null +++ b/src/main/java/lain/lib/SimpleDownloader.java @@ -0,0 +1,213 @@ +package lain.lib; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ManagedBlocker; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public final class SimpleDownloader +{ + + private static Optional connect(URL url, Proxy proxy, Consumer preConnect, Consumer onException) + { + try + { + URLConnection conn = proxy == null ? url.openConnection() : url.openConnection(proxy); + if (preConnect != null) + preConnect.accept(conn); + conn.connect(); + return Optional.of(conn); + } + catch (Throwable e) + { + if (onException != null) + onException.accept(e); + return Optional.empty(); + } + } + + private static boolean deleteIfExists(Path path) + { + try + { + return Files.deleteIfExists(path); + } + catch (IOException e) + { + return false; + } + } + + private static Consumer deleteOnExceptionDecor(Path path, Consumer onException) + { + Consumer deleteOnException = e -> deleteIfExists(path); + return onException == null ? deleteOnException : deleteOnException.andThen(onException); + } + + private static Predicate deleteOnFalseDecor(Path path, Predicate predicate) + { + if (predicate == null) + return null; + return t -> { + if (predicate.test(t)) + return true; + deleteIfExists(path); + return false; + }; + } + + private static Optional download(Path local, URLConnection conn, MessageDigest digest, Predicate shouldTransfer, Consumer onException) + { + try + { + if (shouldTransfer != null && !shouldTransfer.test(conn)) + return Optional.empty(); + try (FileChannel channel = FileChannel.open(local, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) + { + channel.transferFrom(Channels.newChannel(digest == null ? conn.getInputStream() : new DigestInputStream(conn.getInputStream(), digest)), 0L, Long.MAX_VALUE); + return Optional.of(local); + } + } + catch (Throwable e) + { + if (digest != null) + digest.reset(); + if (onException != null) + onException.accept(e); + return Optional.empty(); + } + } + + private static Optional resource(String resource, Consumer onException) + { + try + { + return Optional.of(new URL(resource)); + } + catch (Throwable e) + { + if (onException != null) + onException.accept(e); + return Optional.empty(); + } + } + + private static void runAsync(Runnable runnable, Executor executor) + { + if (executor == null) + CompletableFuture.runAsync(runnable); + else + CompletableFuture.runAsync(runnable, executor); + } + + private static void runBlocky(Runnable runnable, Consumer onException) + { + try + { + ForkJoinPool.managedBlock(new ManagedBlocker() + { + + @Override + public boolean block() throws InterruptedException + { + runnable.run(); + return true; + } + + @Override + public boolean isReleasable() + { + return false; + } + + }); + } + catch (Throwable e) + { + if (onException != null) + onException.accept(e); + } + } + + private static boolean sleep(long millis) + { + try + { + Thread.sleep(millis); + return true; + } + catch (InterruptedException e) + { + return false; + } + } + + public static CompletableFuture> start(String resource) + { + return start(resource, null, null, 2, null, SharedPool::execute, null, null, null); + } + + public static CompletableFuture> start(String resource, Path tempDir, Proxy proxy, int maxRetries, MessageDigest digest, Executor executor, Consumer preExecute, Consumer preConnect, Predicate shouldTransfer) + { + Objects.requireNonNull(resource); + CompletableFuture> future = new CompletableFuture<>(); + runAsync(() -> { + try + { + if (preExecute != null) + preExecute.accept(Thread.currentThread()); + runBlocky(() -> { + resource(resource, future::completeExceptionally).ifPresent(remote -> { + Retries.retrying(() -> { + connect(remote, proxy, preConnect, Retries::rethrow).ifPresent(conn -> { + tempFile(tempDir, future::completeExceptionally).ifPresent(local -> { + download(local, conn, digest, deleteOnFalseDecor(local, shouldTransfer), deleteOnExceptionDecor(local, Retries::rethrow)).ifPresent(result -> future.complete(Optional.of(result))); + }); + }); + }, IOException.class::isInstance, retries -> sleep(1000L), maxRetries).toRunnable(future::completeExceptionally).run(); + }); + }, future::completeExceptionally); + } + finally + { + if (!future.isDone()) + future.complete(Optional.empty()); + } + }, executor); + return future; + } + + private static Optional tempFile(Path tempDir, Consumer onException) + { + try + { + return Optional.of(tempDir == null ? Files.createTempFile(null, null) : Files.createTempFile(tempDir, null, null)); + } + catch (Throwable e) + { + if (onException != null) + onException.accept(e); + return Optional.empty(); + } + } + + private SimpleDownloader() + { + } + +} diff --git a/src/main/java/lain/mods/skinport/init/forge/ForgeSkinPort.java b/src/main/java/lain/mods/skinport/init/forge/ForgeSkinPort.java index 75e8c0c..7e08ab5 100644 --- a/src/main/java/lain/mods/skinport/init/forge/ForgeSkinPort.java +++ b/src/main/java/lain/mods/skinport/init/forge/ForgeSkinPort.java @@ -23,12 +23,12 @@ import lain.mods.skins.api.interfaces.ISkinProvider; import lain.mods.skins.impl.LegacyConversion; import lain.mods.skins.impl.SkinData; -import lain.mods.skins.providers.CrafatarCachedCapeProvider; -import lain.mods.skins.providers.CrafatarCachedSkinProvider; -import lain.mods.skins.providers.CustomServerCachedCapeProvider; -import lain.mods.skins.providers.CustomServerCachedSkinProvider; -import lain.mods.skins.providers.MojangCachedCapeProvider; -import lain.mods.skins.providers.MojangCachedSkinProvider; +import lain.mods.skins.providers.CrafatarCapeProvider; +import lain.mods.skins.providers.CrafatarSkinProvider; +import lain.mods.skins.providers.CustomServerCapeProvider; +import lain.mods.skins.providers.CustomServerSkinProvider; +import lain.mods.skins.providers.MojangCapeProvider; +import lain.mods.skins.providers.MojangSkinProvider; import lain.mods.skins.providers.UserManagedCapeProvider; import lain.mods.skins.providers.UserManagedSkinProvider; import net.minecraftforge.common.MinecraftForge; @@ -127,21 +127,21 @@ public void init(FMLPreInitializationEvent event) SkinProviderAPI.SKIN.clearProviders(); SkinProviderAPI.SKIN.registerProvider(new UserManagedSkinProvider(Paths.get(".", "cachedImages")).withFilter(LegacyConversion.createFilter())); if (useCustomServer) - SkinProviderAPI.SKIN.registerProvider(new CustomServerCachedSkinProvider(Paths.get(".", "cachedImages", "custom"), hostCustomServer).withFilter(LegacyConversion.createFilter())); + SkinProviderAPI.SKIN.registerProvider(new CustomServerSkinProvider().setHost(hostCustomServer).withFilter(LegacyConversion.createFilter())); if (useMojang) - SkinProviderAPI.SKIN.registerProvider(new MojangCachedSkinProvider(Paths.get(".", "cachedImages", "mojang")).withFilter(LegacyConversion.createFilter())); + SkinProviderAPI.SKIN.registerProvider(new MojangSkinProvider().withFilter(LegacyConversion.createFilter())); if (useCrafatar) - SkinProviderAPI.SKIN.registerProvider(new CrafatarCachedSkinProvider(Paths.get(".", "cachedImages", "crafatar")).withFilter(LegacyConversion.createFilter())); + SkinProviderAPI.SKIN.registerProvider(new CrafatarSkinProvider().withFilter(LegacyConversion.createFilter())); SkinProviderAPI.SKIN.registerProvider(new DefaultSkinProvider()); SkinProviderAPI.CAPE.clearProviders(); SkinProviderAPI.CAPE.registerProvider(new UserManagedCapeProvider(Paths.get(".", "cachedImages"))); if (useCustomServer) - SkinProviderAPI.CAPE.registerProvider(new CustomServerCachedCapeProvider(Paths.get(".", "cachedImages", "custom"), hostCustomServer)); + SkinProviderAPI.CAPE.registerProvider(new CustomServerCapeProvider().setHost(hostCustomServer)); if (useMojang) - SkinProviderAPI.CAPE.registerProvider(new MojangCachedCapeProvider(Paths.get(".", "cachedImages", "mojang"))); + SkinProviderAPI.CAPE.registerProvider(new MojangCapeProvider()); if (useCrafatar) - SkinProviderAPI.CAPE.registerProvider(new CrafatarCachedCapeProvider(Paths.get(".", "cachedImages", "crafatar"))); + SkinProviderAPI.CAPE.registerProvider(new CrafatarCapeProvider()); } network.registerPacket(1, PacketGet0.class); diff --git a/src/main/java/lain/mods/skins/api/interfaces/ISkinTexture.java b/src/main/java/lain/mods/skins/api/interfaces/ISkinTexture.java new file mode 100644 index 0000000..33995cd --- /dev/null +++ b/src/main/java/lain/mods/skins/api/interfaces/ISkinTexture.java @@ -0,0 +1,13 @@ +package lain.mods.skins.api.interfaces; + +import java.nio.ByteBuffer; + +public interface ISkinTexture +{ + + /** + * @return the ByteBuffer for this ISkinTexture. may be null. + */ + ByteBuffer getData(); + +} diff --git a/src/main/java/lain/mods/skins/impl/PlayerProfile.java b/src/main/java/lain/mods/skins/impl/PlayerProfile.java index 24fe375..dc2cde9 100644 --- a/src/main/java/lain/mods/skins/impl/PlayerProfile.java +++ b/src/main/java/lain/mods/skins/impl/PlayerProfile.java @@ -50,7 +50,7 @@ public void onSuccess(GameProfile filled) profile.set(filled); } - }); + }, Runnable::run); } } else if (Shared.isOfflinePlayer(key.getId(), key.getName())) // an offline profile that needs resolving @@ -86,10 +86,10 @@ public void onSuccess(GameProfile filled) profile.set(filled); } - }); + }, Runnable::run); } - }); + }, Runnable::run); } else if (key.getProperties().isEmpty()) // an assumed online profile that needs filling { @@ -141,13 +141,13 @@ public void onSuccess(GameProfile filled) profile.set(filled); } - }); + }, Runnable::run); } - }); + }, Runnable::run); } - }); + }, Runnable::run); } return profile; diff --git a/src/main/java/lain/mods/skins/impl/Shared.java b/src/main/java/lain/mods/skins/impl/Shared.java index c2ce574..f739b8b 100644 --- a/src/main/java/lain/mods/skins/impl/Shared.java +++ b/src/main/java/lain/mods/skins/impl/Shared.java @@ -2,13 +2,20 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URLConnection; import java.nio.channels.Channels; +import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; -import java.util.Map; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.function.Supplier; @@ -17,7 +24,10 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; import com.mojang.authlib.GameProfile; +import lain.lib.Retries; import lain.lib.SharedPool; +import lain.lib.SimpleDownloader; +import lain.mods.skins.impl.forge.MinecraftUtils; public class Shared { @@ -27,7 +37,6 @@ private static interface SupplierBlocker extends Supplier, ForkJoinPool.Ma } public static final GameProfile DUMMY = new GameProfile(UUID.fromString("ae9460f5-bf72-468e-89b6-4eead59001ad"), ""); - public static final Map store = new ConcurrentHashMap<>(); private static final Cache offlines = CacheBuilder.newBuilder().weakKeys().build(); @@ -101,14 +110,21 @@ public static byte[] blockyReadFile(File file, byte[] defaultContents, Consumer< if (file == null) return defaultContents; return blockyCall(() -> { - try (FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream()) + try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - fis.getChannel().transferTo(0, Long.MAX_VALUE, Channels.newChannel(baos)); + channel.transferTo(0L, Long.MAX_VALUE, Channels.newChannel(baos)); return baos.toByteArray(); } }, defaultContents, consumer); } + public static CompletableFuture> downloadSkin(String resource, Executor executor) + { + return SimpleDownloader + .start(resource, null, MinecraftUtils.getProxy(), 2, null, executor, null, null, Shared::stopIfHttpClientError) + .thenApply(Shared::readAndDelete); + } + public static boolean isBlank(CharSequence cs) { int strLen; @@ -136,6 +152,19 @@ public static boolean isOfflinePlayer(UUID id, String name) } } + private static Optional readAndDelete(Optional path) + { + try (FileChannel channel = FileChannel.open(path.orElseThrow(FileNotFoundException::new), StandardOpenOption.READ, StandardOpenOption.DELETE_ON_CLOSE); ByteArrayOutputStream baos = new ByteArrayOutputStream()) + { + channel.transferTo(0L, Long.MAX_VALUE, Channels.newChannel(baos)); + return Optional.of(baos.toByteArray()); + } + catch (IOException e) + { + return Optional.empty(); + } + } + public static boolean sleep(long millis) { try @@ -149,6 +178,21 @@ public static boolean sleep(long millis) } } + private static boolean stopIfHttpClientError(URLConnection conn) + { + if (conn instanceof HttpURLConnection) + try + { + if (((HttpURLConnection) conn).getResponseCode() / 100 == 4) + return false; + } + catch (IOException e) + { + Retries.rethrow(e); + } + return true; + } + public static ListenableFuture submitTask(Callable callable) { ListenableFutureTask future; diff --git a/src/main/java/lain/mods/skins/impl/forge/CustomSkinTexture.java b/src/main/java/lain/mods/skins/impl/forge/CustomSkinTexture.java index 30a8f88..a160ed4 100644 --- a/src/main/java/lain/mods/skins/impl/forge/CustomSkinTexture.java +++ b/src/main/java/lain/mods/skins/impl/forge/CustomSkinTexture.java @@ -4,40 +4,46 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import javax.imageio.ImageIO; +import lain.mods.skins.api.interfaces.ISkinTexture; import lain.mods.skins.impl.SkinData; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.client.resources.IResourceManager; import net.minecraft.util.ResourceLocation; -public class CustomSkinTexture extends AbstractTexture +public class CustomSkinTexture extends AbstractTexture implements ISkinTexture { - private final ResourceLocation _location; - private BufferedImage _image; - - public CustomSkinTexture(ResourceLocation location, ByteBuffer data) + private static BufferedImage loadImage(ByteBuffer buf) { - if (data == null) - throw new IllegalArgumentException("buffer must not be null"); - - _location = location; - - try (InputStream in = SkinData.wrapByteBufferAsInputStream(data)) + try (InputStream in = SkinData.wrapByteBufferAsInputStream(buf)) { - _image = ImageIO.read(in); + return ImageIO.read(in); } catch (Throwable t) { - _image = null; + return null; } } - public BufferedImage getImage() + private final ResourceLocation _location; + private WeakReference _data; + + public CustomSkinTexture(ResourceLocation location, ByteBuffer data) + { + _location = location; + if (data == null) + throw new IllegalArgumentException("buffer must not be null"); + _data = new WeakReference(data); + } + + @Override + public ByteBuffer getData() { - return _image; + return _data.get(); } public ResourceLocation getLocation() @@ -50,9 +56,14 @@ public void loadTexture(IResourceManager resMan) throws IOException { deleteGlTexture(); - if (_image == null) + ByteBuffer buf; + if ((buf = _data.get()) == null) // gc throw new FileNotFoundException(getLocation().toString()); - TextureUtil.uploadTextureImageAllocate(getGlTextureId(), _image, false, false); + BufferedImage image; + if ((image = loadImage(buf)) == null) + throw new FileNotFoundException(getLocation().toString()); + + TextureUtil.uploadTextureImageAllocate(getGlTextureId(), image, false, false); } } diff --git a/src/main/java/lain/mods/skins/providers/CachedDownloader.java b/src/main/java/lain/mods/skins/providers/CachedDownloader.java deleted file mode 100644 index 8ce8fd5..0000000 --- a/src/main/java/lain/mods/skins/providers/CachedDownloader.java +++ /dev/null @@ -1,249 +0,0 @@ -package lain.mods.skins.providers; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URL; -import java.net.URLConnection; -import java.nio.channels.Channels; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import lain.mods.skins.impl.Shared; - -public class CachedDownloader -{ - - public static CachedDownloader create() - { - return new CachedDownloader(); - } - - private int _cacheMinTTL = 600; - private Map _dataStore; - private Predicate _handler = code -> true; - private Predicate _validator; - private File _local; - private int _maxTries = 5; - private Proxy _proxy; - private URL _remote; - - private CachedDownloader() - { - } - - private final byte[] doRead() - { - if (_local == null || _remote == null || _dataStore == null) - return null; - if (_local.exists() && (_local.isDirectory() || !_local.canRead() || !_local.canWrite())) - return null; - - String key = Integer.toHexString(_local.hashCode()); - String[] metadata = _dataStore.getOrDefault(key, "0:0:").split(":", 3); - - long size = 0; - long expire = 0; - String etag = ""; - - if (metadata.length == 3) - { - try - { - size = Long.parseLong(metadata[0]); - expire = Long.parseLong(metadata[1]); - etag = metadata[2]; - } - catch (NumberFormatException e) - { - size = 0; - expire = 0; - etag = ""; - } - } - - int tries = 0; - while (tries++ < _maxTries) - { - try - { - boolean expired = _local.exists() && size == _local.length() ? System.currentTimeMillis() > expire : true; - - URLConnection conn = _proxy == null ? _remote.openConnection() : _remote.openConnection(_proxy); - conn.setConnectTimeout(30000); - conn.setReadTimeout(10000); - if (!expired && !etag.isEmpty()) - conn.setRequestProperty("If-None-Match", etag); - conn.connect(); - - if (conn instanceof HttpURLConnection) - { - HttpURLConnection c = (HttpURLConnection) conn; - int code = c.getResponseCode(); - switch (code / 100) - { - case 4: - return null; - case 2: - try (FileOutputStream fos = new FileOutputStream(_local)) - { - fos.getChannel().transferFrom(Channels.newChannel(conn.getInputStream()), 0, Long.MAX_VALUE); - } - break; - default: - if (code != 304 && !_handler.test(code)) - return null; - break; - } - } - else - { - try (FileOutputStream fos = new FileOutputStream(_local)) - { - fos.getChannel().transferFrom(Channels.newChannel(conn.getInputStream()), 0, Long.MAX_VALUE); - } - } - - Map cacheControl = parseField(conn.getHeaderField("Cache-Control")); - if (!cacheControl.containsKey("no-cache")) - { - etag = conn.getHeaderField("Etag"); - int age = 0; - try - { - if (cacheControl.containsKey("max-age")) - age = Integer.parseInt(cacheControl.get("max-age")); - } - catch (NumberFormatException e) - { - age = 0; - } - expire = Math.max(age > 0 ? (System.currentTimeMillis() + (age * 1000)) : conn.getExpiration(), System.currentTimeMillis() + (_cacheMinTTL * 1000)); - size = _local.length(); - _dataStore.put(key, size + ":" + expire + ":" + etag); - } - else - { - _dataStore.remove(key); - } - - byte[] contents; - if ((contents = Shared.blockyReadFile(_local, null, null)) != null && (_validator == null || _validator.test(contents))) - return contents; - } - catch (IOException e) - { - } - if (tries < _maxTries && !Shared.sleep(1000L)) // wait 1 second before retry - break; // interrupted - } - - return null; - } - - private Map parseField(String field) - { - return Pattern.compile(",").splitAsStream(field == null ? "" : field).map(String::trim).collect(HashMap::new, (m, s) -> { - String[] as = s.split("=", 2); - m.put(as[0], as.length == 2 ? as[1] : null); - }, HashMap::putAll); - } - - public byte[] read() - { - return Shared.blockyCall(() -> { - return doRead(); - }, null, null); - } - - public CachedDownloader setCacheMinTTL(int cacheMinTTL) - { - _cacheMinTTL = cacheMinTTL; - return this; - } - - public CachedDownloader setDataStore(Map dataStore) - { - _dataStore = dataStore; - return this; - } - - public CachedDownloader setErrorCodeHandler(Predicate handler) - { - _handler = handler; - return this; - } - - public CachedDownloader setLocal(File local) - { - _local = local; - return this; - } - - public CachedDownloader setLocal(File dir, String filename) - { - _local = new File(dir, filename); - return this; - } - - public CachedDownloader setLocal(File dir, String format, Object... args) - { - _local = new File(dir, String.format(format, args)); - return this; - } - - public CachedDownloader setMaxTries(int maxTries) - { - _maxTries = maxTries; - return this; - } - - public CachedDownloader setProxy(Proxy proxy) - { - _proxy = proxy; - return this; - } - - public CachedDownloader setRemote(String remote) - { - try - { - _remote = new URL(remote); - } - catch (MalformedURLException e) - { - _remote = null; - } - return this; - } - - public CachedDownloader setRemote(String format, Object... args) - { - try - { - _remote = new URL(String.format(format, args)); - } - catch (MalformedURLException e) - { - _remote = null; - } - return this; - } - - public CachedDownloader setRemote(URL remote) - { - _remote = remote; - return this; - } - - public CachedDownloader setValidator(Predicate validator) - { - _validator = validator; - return this; - } - -} diff --git a/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java deleted file mode 100644 index 10cf448..0000000 --- a/src/main/java/lain/mods/skins/providers/CrafatarCachedCapeProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -package lain.mods.skins.providers; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.UUID; -import java.util.function.Function; -import lain.lib.SharedPool; -import lain.mods.skins.api.interfaces.IPlayerProfile; -import lain.mods.skins.api.interfaces.ISkin; -import lain.mods.skins.api.interfaces.ISkinProvider; -import lain.mods.skins.impl.Shared; -import lain.mods.skins.impl.SkinData; -import lain.mods.skins.impl.forge.MinecraftUtils; - -public class CrafatarCachedCapeProvider implements ISkinProvider -{ - - private File _dirN; - private File _dirU; - private Function _filter; - - public CrafatarCachedCapeProvider(Path workDir) - { - _dirN = new File(workDir.toFile(), "capes"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - - @Override - public ISkin getSkin(IPlayerProfile profile) - { - SkinData skin = new SkinData(); - if (_filter != null) - skin.setSkinFilter(_filter); - SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); - if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote("https://crafatar.com/capes/%s", uuid).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, "cape"); - }); - return skin; - } - - public CrafatarCachedCapeProvider withFilter(Function filter) - { - _filter = filter; - return this; - } - -} diff --git a/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java deleted file mode 100644 index 79c5a05..0000000 --- a/src/main/java/lain/mods/skins/providers/CrafatarCachedSkinProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -package lain.mods.skins.providers; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.UUID; -import java.util.function.Function; -import lain.lib.SharedPool; -import lain.mods.skins.api.interfaces.IPlayerProfile; -import lain.mods.skins.api.interfaces.ISkin; -import lain.mods.skins.api.interfaces.ISkinProvider; -import lain.mods.skins.impl.Shared; -import lain.mods.skins.impl.SkinData; -import lain.mods.skins.impl.forge.MinecraftUtils; - -public class CrafatarCachedSkinProvider implements ISkinProvider -{ - - private File _dirN; - private File _dirU; - private Function _filter; - - public CrafatarCachedSkinProvider(Path workDir) - { - _dirN = new File(workDir.toFile(), "skins"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - - @Override - public ISkin getSkin(IPlayerProfile profile) - { - SkinData skin = new SkinData(); - if (_filter != null) - skin.setSkinFilter(_filter); - SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); - if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote("https://crafatar.com/skins/%s", uuid).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, SkinData.judgeSkinType(data)); - }); - return skin; - } - - public CrafatarCachedSkinProvider withFilter(Function filter) - { - _filter = filter; - return this; - } - -} diff --git a/src/main/java/lain/mods/skins/providers/CrafatarCapeProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarCapeProvider.java new file mode 100644 index 0000000..89231d7 --- /dev/null +++ b/src/main/java/lain/mods/skins/providers/CrafatarCapeProvider.java @@ -0,0 +1,42 @@ +package lain.mods.skins.providers; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.function.Function; +import lain.lib.SharedPool; +import lain.mods.skins.api.interfaces.IPlayerProfile; +import lain.mods.skins.api.interfaces.ISkin; +import lain.mods.skins.api.interfaces.ISkinProvider; +import lain.mods.skins.impl.Shared; +import lain.mods.skins.impl.SkinData; + +public class CrafatarCapeProvider implements ISkinProvider +{ + + private Function _filter; + + @Override + public ISkin getSkin(IPlayerProfile profile) + { + SkinData skin = new SkinData(); + if (_filter != null) + skin.setSkinFilter(_filter); + SharedPool.execute(() -> { + if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) + { + Shared.downloadSkin(String.format("https://crafatar.com/capes/%s", profile.getPlayerID()), Runnable::run).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, "cape"); + }); + } + }); + return skin; + } + + public CrafatarCapeProvider withFilter(Function filter) + { + _filter = filter; + return this; + } + +} diff --git a/src/main/java/lain/mods/skins/providers/CrafatarSkinProvider.java b/src/main/java/lain/mods/skins/providers/CrafatarSkinProvider.java new file mode 100644 index 0000000..f9d3545 --- /dev/null +++ b/src/main/java/lain/mods/skins/providers/CrafatarSkinProvider.java @@ -0,0 +1,42 @@ +package lain.mods.skins.providers; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.function.Function; +import lain.lib.SharedPool; +import lain.mods.skins.api.interfaces.IPlayerProfile; +import lain.mods.skins.api.interfaces.ISkin; +import lain.mods.skins.api.interfaces.ISkinProvider; +import lain.mods.skins.impl.Shared; +import lain.mods.skins.impl.SkinData; + +public class CrafatarSkinProvider implements ISkinProvider +{ + + private Function _filter; + + @Override + public ISkin getSkin(IPlayerProfile profile) + { + SkinData skin = new SkinData(); + if (_filter != null) + skin.setSkinFilter(_filter); + SharedPool.execute(() -> { + if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) + { + Shared.downloadSkin(String.format("https://crafatar.com/skins/%s", profile.getPlayerID()), Runnable::run).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, SkinData.judgeSkinType(data)); + }); + } + }); + return skin; + } + + public CrafatarSkinProvider withFilter(Function filter) + { + _filter = filter; + return this; + } + +} diff --git a/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java deleted file mode 100644 index f237391..0000000 --- a/src/main/java/lain/mods/skins/providers/CustomServerCachedCapeProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package lain.mods.skins.providers; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.UUID; -import java.util.function.Function; -import lain.lib.SharedPool; -import lain.mods.skins.api.interfaces.IPlayerProfile; -import lain.mods.skins.api.interfaces.ISkin; -import lain.mods.skins.api.interfaces.ISkinProvider; -import lain.mods.skins.impl.Shared; -import lain.mods.skins.impl.SkinData; -import lain.mods.skins.impl.forge.MinecraftUtils; - -public class CustomServerCachedCapeProvider implements ISkinProvider -{ - - private File _dirN; - private File _dirU; - private Function _filter; - private String _host; - - public CustomServerCachedCapeProvider(Path workDir, String host) - { - _dirN = new File(workDir.toFile(), "capes"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - _host = host; - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - - @Override - public ISkin getSkin(IPlayerProfile profile) - { - SkinData skin = new SkinData(); - if (_filter != null) - skin.setSkinFilter(_filter); - SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); - String name = profile.getPlayerName(); - if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote("%s/capes/%s", _host, uuid).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data == null && !Shared.isBlank(name)) - data = CachedDownloader.create().setLocal(_dirN, name).setRemote("%s/capes/%s", _host, name).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, "cape"); - }); - return skin; - } - - public CustomServerCachedCapeProvider withFilter(Function filter) - { - _filter = filter; - return this; - } - -} diff --git a/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java deleted file mode 100644 index c428a58..0000000 --- a/src/main/java/lain/mods/skins/providers/CustomServerCachedSkinProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package lain.mods.skins.providers; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.UUID; -import java.util.function.Function; -import lain.lib.SharedPool; -import lain.mods.skins.api.interfaces.IPlayerProfile; -import lain.mods.skins.api.interfaces.ISkin; -import lain.mods.skins.api.interfaces.ISkinProvider; -import lain.mods.skins.impl.Shared; -import lain.mods.skins.impl.SkinData; -import lain.mods.skins.impl.forge.MinecraftUtils; - -public class CustomServerCachedSkinProvider implements ISkinProvider -{ - - private File _dirN; - private File _dirU; - private Function _filter; - private String _host; - - public CustomServerCachedSkinProvider(Path workDir, String host) - { - _dirN = new File(workDir.toFile(), "skins"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - _host = host; - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - - @Override - public ISkin getSkin(IPlayerProfile profile) - { - SkinData skin = new SkinData(); - if (_filter != null) - skin.setSkinFilter(_filter); - SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); - String name = profile.getPlayerName(); - if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote("%s/skins/%s", _host, uuid).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data == null && !Shared.isBlank(name)) - data = CachedDownloader.create().setLocal(_dirN, name).setRemote("%s/skins/%s", _host, name).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, SkinData.judgeSkinType(data)); - }); - return skin; - } - - public CustomServerCachedSkinProvider withFilter(Function filter) - { - _filter = filter; - return this; - } - -} diff --git a/src/main/java/lain/mods/skins/providers/CustomServerCapeProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerCapeProvider.java new file mode 100644 index 0000000..0d2f807 --- /dev/null +++ b/src/main/java/lain/mods/skins/providers/CustomServerCapeProvider.java @@ -0,0 +1,51 @@ +package lain.mods.skins.providers; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import lain.lib.SharedPool; +import lain.mods.skins.api.interfaces.IPlayerProfile; +import lain.mods.skins.api.interfaces.ISkin; +import lain.mods.skins.api.interfaces.ISkinProvider; +import lain.mods.skins.impl.Shared; +import lain.mods.skins.impl.SkinData; + +public class CustomServerCapeProvider implements ISkinProvider +{ + + private Function _filter; + private String _host; + + @Override + public ISkin getSkin(IPlayerProfile profile) + { + SkinData skin = new SkinData(); + if (_filter != null) + skin.setSkinFilter(_filter); + SharedPool.execute(() -> { + Shared.downloadSkin(String.format("%s/capes/%s", _host, profile.getPlayerID()), Runnable::run).handle((r, t) -> { + if (r != null && r.isPresent()) + return CompletableFuture.completedFuture(r); + return Shared.downloadSkin(String.format("%s/capes/%s", _host, profile.getPlayerName()), Runnable::run); + }).thenCompose(Function.identity()).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, "cape"); + }); + }); + return skin; + } + + public CustomServerCapeProvider setHost(String host) + { + _host = host; + return this; + } + + public CustomServerCapeProvider withFilter(Function filter) + { + _filter = filter; + return this; + } + +} diff --git a/src/main/java/lain/mods/skins/providers/CustomServerSkinProvider.java b/src/main/java/lain/mods/skins/providers/CustomServerSkinProvider.java new file mode 100644 index 0000000..e3be500 --- /dev/null +++ b/src/main/java/lain/mods/skins/providers/CustomServerSkinProvider.java @@ -0,0 +1,51 @@ +package lain.mods.skins.providers; + +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import lain.lib.SharedPool; +import lain.mods.skins.api.interfaces.IPlayerProfile; +import lain.mods.skins.api.interfaces.ISkin; +import lain.mods.skins.api.interfaces.ISkinProvider; +import lain.mods.skins.impl.Shared; +import lain.mods.skins.impl.SkinData; + +public class CustomServerSkinProvider implements ISkinProvider +{ + + private Function _filter; + private String _host; + + @Override + public ISkin getSkin(IPlayerProfile profile) + { + SkinData skin = new SkinData(); + if (_filter != null) + skin.setSkinFilter(_filter); + SharedPool.execute(() -> { + Shared.downloadSkin(String.format("%s/skins/%s", _host, profile.getPlayerID()), Runnable::run).handle((r, t) -> { + if (r != null && r.isPresent()) + return CompletableFuture.completedFuture(r); + return Shared.downloadSkin(String.format("%s/skins/%s", _host, profile.getPlayerName()), Runnable::run); + }).thenCompose(Function.identity()).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, SkinData.judgeSkinType(data)); + }); + }); + return skin; + } + + public CustomServerSkinProvider setHost(String host) + { + _host = host; + return this; + } + + public CustomServerSkinProvider withFilter(Function filter) + { + _filter = filter; + return this; + } + +} diff --git a/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java b/src/main/java/lain/mods/skins/providers/MojangCapeProvider.java similarity index 57% rename from src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java rename to src/main/java/lain/mods/skins/providers/MojangCapeProvider.java index bec5066..cc912cd 100644 --- a/src/main/java/lain/mods/skins/providers/MojangCachedCapeProvider.java +++ b/src/main/java/lain/mods/skins/providers/MojangCapeProvider.java @@ -1,10 +1,8 @@ package lain.mods.skins.providers; -import java.io.File; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.Map; -import java.util.UUID; +import java.util.Optional; import java.util.function.Function; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; @@ -16,28 +14,11 @@ import lain.mods.skins.impl.SkinData; import lain.mods.skins.impl.forge.MinecraftUtils; -public class MojangCachedCapeProvider implements ISkinProvider +public class MojangCapeProvider implements ISkinProvider { - private File _dirN; - private File _dirU; private Function _filter; - public MojangCachedCapeProvider(Path workDir) - { - _dirN = new File(workDir.toFile(), "capes"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - @Override public ISkin getSkin(IPlayerProfile profile) { @@ -45,24 +26,23 @@ public ISkin getSkin(IPlayerProfile profile) if (_filter != null) skin.setSkinFilter(_filter); SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) { Map textures = MinecraftUtils.getSessionService().getTextures((GameProfile) profile.getOriginal(), false); if (textures != null && textures.containsKey(MinecraftProfileTexture.Type.CAPE)) { MinecraftProfileTexture tex = textures.get(MinecraftProfileTexture.Type.CAPE); - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote(tex.getUrl()).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, "cape"); + Shared.downloadSkin(tex.getUrl(), Runnable::run).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, "cape"); + }); } } }); return skin; } - public MojangCachedCapeProvider withFilter(Function filter) + public MojangCapeProvider withFilter(Function filter) { _filter = filter; return this; diff --git a/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java b/src/main/java/lain/mods/skins/providers/MojangSkinProvider.java similarity index 55% rename from src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java rename to src/main/java/lain/mods/skins/providers/MojangSkinProvider.java index 829a89a..0d99c33 100644 --- a/src/main/java/lain/mods/skins/providers/MojangCachedSkinProvider.java +++ b/src/main/java/lain/mods/skins/providers/MojangSkinProvider.java @@ -1,10 +1,8 @@ package lain.mods.skins.providers; -import java.io.File; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.Map; -import java.util.UUID; +import java.util.Optional; import java.util.function.Function; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; @@ -16,28 +14,11 @@ import lain.mods.skins.impl.SkinData; import lain.mods.skins.impl.forge.MinecraftUtils; -public class MojangCachedSkinProvider implements ISkinProvider +public class MojangSkinProvider implements ISkinProvider { - private File _dirN; - private File _dirU; private Function _filter; - public MojangCachedSkinProvider(Path workDir) - { - _dirN = new File(workDir.toFile(), "skins"); - _dirN.mkdirs(); - _dirU = new File(_dirN, "uuid"); - _dirU.mkdirs(); - - for (File file : _dirN.listFiles()) - if (file.isFile()) - file.delete(); - for (File file : _dirU.listFiles()) - if (file.isFile()) - file.delete(); - } - @Override public ISkin getSkin(IPlayerProfile profile) { @@ -45,24 +26,23 @@ public ISkin getSkin(IPlayerProfile profile) if (_filter != null) skin.setSkinFilter(_filter); SharedPool.execute(() -> { - byte[] data = null; - UUID uuid = profile.getPlayerID(); if (!Shared.isOfflinePlayer(profile.getPlayerID(), profile.getPlayerName())) { Map textures = MinecraftUtils.getSessionService().getTextures((GameProfile) profile.getOriginal(), false); if (textures != null && textures.containsKey(MinecraftProfileTexture.Type.SKIN)) { MinecraftProfileTexture tex = textures.get(MinecraftProfileTexture.Type.SKIN); - data = CachedDownloader.create().setLocal(_dirU, uuid.toString()).setRemote(tex.getUrl()).setDataStore(Shared.store).setProxy(MinecraftUtils.getProxy()).setValidator(SkinData::validateData).read(); - if (data != null) - skin.put(data, SkinData.judgeSkinType(data)); // "slim".equals(tex.getMetadata("model")) ? "slim" : "default" + Shared.downloadSkin(tex.getUrl(), Runnable::run).thenApply(Optional::get).thenAccept(data -> { + if (SkinData.validateData(data)) + skin.put(data, SkinData.judgeSkinType(data)); + }); } } }); return skin; } - public MojangCachedSkinProvider withFilter(Function filter) + public MojangSkinProvider withFilter(Function filter) { _filter = filter; return this;