-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
930 additions
and
598 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<URLConnection> connect(URL url, Proxy proxy, Consumer<URLConnection> preConnect, Consumer<Throwable> 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<Throwable> deleteOnExceptionDecor(Path path, Consumer<Throwable> onException) | ||
{ | ||
Consumer<Throwable> deleteOnException = e -> deleteIfExists(path); | ||
return onException == null ? deleteOnException : deleteOnException.andThen(onException); | ||
} | ||
|
||
private static <T> Predicate<T> deleteOnFalseDecor(Path path, Predicate<T> predicate) | ||
{ | ||
if (predicate == null) | ||
return null; | ||
return t -> { | ||
if (predicate.test(t)) | ||
return true; | ||
deleteIfExists(path); | ||
return false; | ||
}; | ||
} | ||
|
||
private static Optional<Path> download(Path local, URLConnection conn, MessageDigest digest, Predicate<URLConnection> shouldTransfer, Consumer<Throwable> 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<URL> resource(String resource, Consumer<Throwable> 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<Throwable> 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<Optional<Path>> start(String resource) | ||
{ | ||
return start(resource, null, null, 2, null, SharedPool::execute, null, null, null); | ||
} | ||
|
||
public static CompletableFuture<Optional<Path>> start(String resource, Path tempDir, Proxy proxy, int maxRetries, MessageDigest digest, Executor executor, Consumer<Thread> preExecute, Consumer<URLConnection> preConnect, Predicate<URLConnection> shouldTransfer) | ||
{ | ||
Objects.requireNonNull(resource); | ||
CompletableFuture<Optional<Path>> 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<Path> tempFile(Path tempDir, Consumer<Throwable> 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() | ||
{ | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/lain/mods/skins/api/interfaces/ISkinTexture.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.