diff --git a/pom.xml b/pom.xml index ad2417d..2f19b81 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,18 @@ + + net.alchim31.maven + scala-maven-plugin + 3.4.2 + + + + compile + + + + @@ -129,6 +141,11 @@ placeholderapi 2.8.4 + + org.scala-lang + scala-library + 2.12.7 + bukkit bukkit1_12 diff --git a/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java b/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java new file mode 100644 index 0000000..13919a0 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java @@ -0,0 +1,4 @@ +package com.ilummc.eagletdl; + +public class AlreadyStartException extends RuntimeException { +} diff --git a/src/main/java/com/ilummc/eagletdl/CompleteEvent.java b/src/main/java/com/ilummc/eagletdl/CompleteEvent.java new file mode 100644 index 0000000..839625b --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/CompleteEvent.java @@ -0,0 +1,19 @@ +package com.ilummc.eagletdl; + +public class CompleteEvent { + private EagletTask task; + private boolean success; + + CompleteEvent(EagletTask task, boolean success) { + this.task = task; + this.success = success; + } + + public boolean isSuccess() { + return success; + } + + public EagletTask getTask() { + return task; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java b/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java new file mode 100644 index 0000000..5299f27 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java @@ -0,0 +1,27 @@ +package com.ilummc.eagletdl; + +public class ConnectedEvent { + + private long contentLength; + private EagletTask task; + + public ConnectedEvent(long length, EagletTask task) { + this.contentLength = length; + this.task = task; + } + + /** + * Get the length of the download task. + *

+ * If the length is -1, this task cannot be downloaded in multiple threads. + * + * @return length + */ + public long getContentLength() { + return contentLength; + } + + public EagletTask getTask() { + return task; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java b/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java new file mode 100644 index 0000000..0c59692 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java @@ -0,0 +1,4 @@ +package com.ilummc.eagletdl; + +public class DoNotSupportMultipleThreadException extends RuntimeException { +} diff --git a/src/main/java/com/ilummc/eagletdl/Eaglet.java b/src/main/java/com/ilummc/eagletdl/Eaglet.java new file mode 100644 index 0000000..7e000b3 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/Eaglet.java @@ -0,0 +1,22 @@ +package com.ilummc.eagletdl; + +/** + * Test class + */ +public class Eaglet { + + public static void main(String[] args) { + //new EagletTask().url("http://sgp-ping.vultr.com/vultr.com.100MB.bin") + new EagletTask().url("https://gitee.com/bkm016/TabooLibCloud/raw/master/TabooMenu/TabooMenu.jar") + .file("F:\\test.dl") + .setThreads(1) + .readTimeout(1000) + .connectionTimeout(1000) + .maxRetry(30) + .setOnConnected(event -> System.out.println(event.getContentLength())) + .setOnProgress(event -> System.out.println(event.getSpeedFormatted() + " " + event.getPercentageFormatted())) + .setOnComplete(event -> System.out.println("Complete")) + .start(); + } + +} diff --git a/src/main/java/com/ilummc/eagletdl/EagletHandler.java b/src/main/java/com/ilummc/eagletdl/EagletHandler.java new file mode 100644 index 0000000..ef63e4a --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/EagletHandler.java @@ -0,0 +1,8 @@ +package com.ilummc.eagletdl; + +@FunctionalInterface +public interface EagletHandler { + + void handle(T event) ; + +} diff --git a/src/main/java/com/ilummc/eagletdl/EagletTask.java b/src/main/java/com/ilummc/eagletdl/EagletTask.java new file mode 100644 index 0000000..fb85bdb --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/EagletTask.java @@ -0,0 +1,460 @@ +package com.ilummc.eagletdl; + +import java.io.File; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class EagletTask { + + private ReentrantLock lock = new ReentrantLock(); + + Map httpHeader = new ConcurrentHashMap<>(); + + private URL url; + + EagletHandler onError = event -> event.getException().printStackTrace(); + + private EagletHandler onStart; + + private EagletHandler onComplete; + + private EagletHandler onConnected; + + private EagletHandler onProgress; + + private Proxy proxy; + + private String md5, sha1, sha256; + + String requestMethod = "GET"; + + private int threadAmount = 1; + + int connectionTimeout = 7000; + int readTimeout = 7000; + int maxRetry = 3; + + private File dest; + + private transient boolean running = false; + private transient long contentLength, maxBlockingTime = 7000; + private transient ExecutorService executorService; + private transient Thread monitor; + + public EagletTask() { + } + + /** + * Stop this task forcefully, and the target file will not be removed. + */ + public void stop() { + executorService.shutdownNow(); + monitor.interrupt(); + } + + /** + * Start the download file + *

+ * 开始下载文件 + */ + public EagletTask start() { + // create thread pool for download + executorService = Executors.newFixedThreadPool(threadAmount); + // check if is already running + if (running) throw new AlreadyStartException(); + // start the monitor thread + monitor = new Thread(() -> { + lock.lock(); + // fire a new start event + if (onStart != null) onStart.handle(new StartEvent(this)); + try { + // create the target file + if (!dest.exists()) dest.createNewFile(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + // set the connection properties + httpHeader.forEach(connection::addRequestProperty); + connection.setRequestMethod(requestMethod); + connection.setConnectTimeout(30000); + connection.setReadTimeout(30000); + connection.connect(); + contentLength = connection.getContentLengthLong(); + // fire a new connected event + // contains connection properties + if (onConnected != null) onConnected.handle(new ConnectedEvent(contentLength, this)); + // if this is an unknown length task + if (contentLength == -1 || threadAmount == 1) { + // pass the connection instance to this new thread + SingleThreadDownload download = new SingleThreadDownload(connection, dest, this); + executorService.execute(download); + long last = 0; + while (true) { + Thread.sleep(1000); + // check the progress + long progress = download.getCurrentProgress(); + // fire a new progress event + if (onProgress != null) + onProgress.handle(new ProgressEvent(progress - last, this, + ((double) progress) / Math.max((double) contentLength, 0D))); + last = progress; + // check complete + if (last == contentLength || download.isComplete()) { + break; + } + } + // close the thread pool, release resources + executorService.shutdown(); + // change the running flag to false + running = false; + } else { + List splitDownloads = new ArrayList<>(); + // Assign download task length + long blockSize = contentLength / threadAmount; + for (int threadId = 0; threadId < threadAmount; threadId++) { + long startIndex = threadId * blockSize; + long endIndex = (threadId + 1) * blockSize - 1; + if (threadId == (threadAmount - 1)) { + endIndex = contentLength - 1; + } + SplitDownload download = new SplitDownload(url, startIndex, endIndex, dest, this); + // Start downloading + executorService.execute(download); + splitDownloads.add(download); + } + long last = 0; + while (true) { + Thread.sleep(1000); + long progress = 0; + // Collect download progress + for (SplitDownload splitDownload : splitDownloads) { + progress += splitDownload.getCurrentIndex() - splitDownload.startIndex; + // blocked then restart from current index + if (!splitDownload.isComplete() && + System.currentTimeMillis() - splitDownload.getLastUpdateTime() > maxBlockingTime) { + splitDownload.setStartIndex(splitDownload.getCurrentIndex()); + if (splitDownload.getRetry() <= maxRetry) + executorService.execute(splitDownload); + else throw new RetryFailedException(this); + } + } + // Fire a progress event + if (onProgress != null) + onProgress.handle(new ProgressEvent(progress - last, this, + ((double) progress) / ((double) contentLength))); + last = progress; + // check complete + if (last >= contentLength) { + break; + } + } + // close the thread pool, release resources + executorService.shutdown(); + // change the running flag to false + running = false; + } + // check hash + if (md5 != null && !md5.equalsIgnoreCase(HashUtil.md5(dest))) + throw new HashNotMatchException(); + if (sha1 != null && !sha1.equalsIgnoreCase(HashUtil.sha1(dest))) + throw new HashNotMatchException(); + if (sha256 != null && !sha256.equalsIgnoreCase(HashUtil.sha256(dest))) + throw new HashNotMatchException(); + if (onComplete != null) onComplete.handle(new CompleteEvent(this, true)); + } catch (Exception e) { + onError.handle(new ErrorEvent(e, this)); + executorService.shutdown(); + if (onComplete != null) onComplete.handle(new CompleteEvent(this, false)); + } finally { + lock.unlock(); + } + }, "EagletTaskMonitor"); + monitor.start(); + return this; + } + + public EagletTask waitUntil() { + while (lock.tryLock()) lock.unlock(); + lock.lock(); + lock.unlock(); + return this; + } + + public EagletTask waitFor(long timeout, TimeUnit unit) { + while (lock.tryLock()) lock.unlock(); + try { + lock.tryLock(timeout, unit); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return this; + } + + public EagletTask maxRetry(int maxRetry) { + this.maxRetry = maxRetry; + return this; + } + + /** + * Set the sha256 hash of the download file. Case is not sensitive. + *

+ * If the hash check failed, an error event will be fired. + * + * @param sha256 file sha1 + * @return task instance + */ + public EagletTask sha256(String sha256) { + this.sha256 = sha256; + return this; + } + + /** + * Set the sha1 hash of the download file. Case is not sensitive. + *

+ * If the hash check failed, an error event will be fired. + * + * @param sha1 file sha1 + * @return task instance + */ + public EagletTask sha1(String sha1) { + this.sha1 = sha1; + return this; + } + + /** + * Set the md5 hash of the download file. Case is not sensitive. + *

+ * If the hash check failed, an error event will be fired. + * + * @param md5 file md5 + * @return task instance + */ + public EagletTask md5(String md5) { + this.md5 = md5; + return this; + } + + /** + * Set the max blocked time per download thread. + *

+ * If the thread blocks exceeded the provided time, this thread will re-start the task. + * + * @param maxBlockingTime time + * @return task instance + */ + private EagletTask maxBlocking(long maxBlockingTime) { + this.maxBlockingTime = maxBlockingTime; + return this; + } + + /** + * Set the progress handler + *

+ * This handler will be called every 1000 milli seconds. + *

+ * 设置处理进度的时间监听器。该监听器的 handle 方法每秒调用一次。 + * + * @param onProgress handler + * @return task instance + */ + public EagletTask setOnProgress(EagletHandler onProgress) { + this.onProgress = onProgress; + return this; + } + + /** + * Set the download file + * + * @param file the file's absolute path + * @return task instance + */ + public EagletTask file(String file) { + this.dest = new File(file); + return this; + } + + /** + * Set the download file + * + * @param file the file + * @return task instance + */ + public EagletTask file(File file) { + this.dest = file; + return this; + } + + /** + * Set the connected handler + *

+ * This will be called when the connection is established + *

+ * Async call + * + * @param onConnected onConnected event handler + * @return task instance + */ + public EagletTask setOnConnected(EagletHandler onConnected) { + this.onConnected = onConnected; + return this; + } + + /** + * Set the read timeout, default is 7000 + * + * @param timeout timeout + * @return task instance + */ + public EagletTask readTimeout(int timeout) { + this.readTimeout = timeout; + return this; + } + + /** + * Set the connection timeout, default is 7000 + * + * @param timeout timeout + * @return task instance + */ + public EagletTask connectionTimeout(int timeout) { + this.connectionTimeout = timeout; + return this; + } + + /** + * Set the request method, default is GET + * + * @param requestMethod the request method + * @return task instance + */ + public EagletTask requestMethod(String requestMethod) { + this.requestMethod = requestMethod; + return this; + } + + /** + * Set the complete event handler + *

+ * This handler will be called when everything is complete, and the downloaded file is available + *

+ * Async call + * + * @param onComplete the handler + * @return task instance + */ + public EagletTask setOnComplete(EagletHandler onComplete) { + this.onComplete = onComplete; + return this; + } + + /** + * Set the start handler + *

+ * This handler will be called when the start method is called + *

+ * Async call + * + * @param onStart the handler + * @return task instance + */ + public EagletTask setOnStart(EagletHandler onStart) { + this.onStart = onStart; + return this; + } + + /** + * Set the network proxy + * + * @param proxy the proxy + * @return task instance + */ + public EagletTask proxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * Set the error handler, default is to print the stack trace + *

+ * This handler will be called when an exception is thrown + *

+ * Async call + * + * @param onError the handler + * @return task instance + */ + public EagletTask setOnError(EagletHandler onError) { + this.onError = onError; + return this; + } + + + /** + * Set how much thread should be used to download, default is 1 + * + * @param i thread amount + * @return task instance + */ + public EagletTask setThreads(int i) { + if (i < 1) throw new RuntimeException("Thread amount cannot be zero or negative!"); + threadAmount = i; + return this; + } + + /** + * Set the download source + * + * @param url the url + * @return task instance + */ + public EagletTask url(URL url) { + this.url = url; + return this; + } + + /** + * Set the download source + * + * @param url the url + * @return task instance + */ + public EagletTask url(String url) { + try { + this.url = new URL(url); + } catch (MalformedURLException e) { + onError.handle(new ErrorEvent(e, this)); + } + return this; + } + + /** + * Clear the http header field + * + * @return task instance + */ + public EagletTask clearHeaders() { + httpHeader.clear(); + return this; + } + + /** + * Set the header field of the http request + * + * @param key header key + * @param value header value + * @return builder instance + */ + public EagletTask header(String key, String value) { + httpHeader.put(key, value); + return this; + } + +} diff --git a/src/main/java/com/ilummc/eagletdl/ErrorEvent.java b/src/main/java/com/ilummc/eagletdl/ErrorEvent.java new file mode 100644 index 0000000..801ac9d --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/ErrorEvent.java @@ -0,0 +1,20 @@ +package com.ilummc.eagletdl; + +public class ErrorEvent { + + private Throwable e; + private EagletTask task; + + public ErrorEvent(Throwable e, EagletTask task) { + this.e = e; + this.task = task; + } + + public EagletTask getTask() { + return task; + } + + public Throwable getException() { + return e; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/HashNotMatchException.java b/src/main/java/com/ilummc/eagletdl/HashNotMatchException.java new file mode 100644 index 0000000..bdb3bcd --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/HashNotMatchException.java @@ -0,0 +1,4 @@ +package com.ilummc.eagletdl; + +public class HashNotMatchException extends RuntimeException { +} diff --git a/src/main/java/com/ilummc/eagletdl/HashUtil.java b/src/main/java/com/ilummc/eagletdl/HashUtil.java new file mode 100644 index 0000000..10bf013 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/HashUtil.java @@ -0,0 +1,63 @@ +package com.ilummc.eagletdl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class HashUtil { + + public static String sha256(File file) { + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("SHA256"); + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + md.update(buffer, 0, length); + } + BigInteger bigInt = new BigInteger(1, md.digest()); + return bigInt.toString(16); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + return null; + } + + public static String sha1(File file) { + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("SHA1"); + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + md.update(buffer, 0, length); + } + BigInteger bigInt = new BigInteger(1, md.digest()); + return bigInt.toString(16); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + return null; + } + + public static String md5(File file) { + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + md.update(buffer, 0, length); + } + BigInteger bigInt = new BigInteger(1, md.digest()); + return bigInt.toString(16); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/com/ilummc/eagletdl/ProgressEvent.java b/src/main/java/com/ilummc/eagletdl/ProgressEvent.java new file mode 100644 index 0000000..f6cf1b7 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/ProgressEvent.java @@ -0,0 +1,53 @@ +package com.ilummc.eagletdl; + +import java.text.DecimalFormat; + +public class ProgressEvent { + + private long speed; + private EagletTask task; + private double percentage; + + ProgressEvent(long speed, EagletTask task, double percentage) { + this.speed = speed; + this.task = task; + this.percentage = percentage; + } + + public EagletTask getTask() { + return task; + } + + public long getSpeed() { + return speed; + } + + public double getPercentage() { + return percentage; + } + + public String getPercentageFormatted() { + return formatDouble(percentage * 100D) + " %"; + } + + /** + * Get the speed with format like X.00 MiB, Y.50 GiB, etc. + * + * @return formatted speed string + */ + public String getSpeedFormatted() { + return format(getSpeed()); + } + + private static String formatDouble(double d) { + return new DecimalFormat("0.00").format(d); + } + + public static String format(long l) { + if (l < 1024) return l + " B"; + if (l < 1024 * 1024) return formatDouble((double) l / 1024D) + " KiB"; + if (l < 1024 * 1024 * 1024) return formatDouble((double) l / (1024D * 1024D)) + " MiB"; + if (l < 1024 * 1024 * 1024 * 1024L) return formatDouble((double) l / (1024D * 1024D * 1024)) + " GiB"; + return ""; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/RetryFailedException.java b/src/main/java/com/ilummc/eagletdl/RetryFailedException.java new file mode 100644 index 0000000..e8a022d --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/RetryFailedException.java @@ -0,0 +1,14 @@ +package com.ilummc.eagletdl; + +public class RetryFailedException extends RuntimeException { + + private EagletTask task; + + RetryFailedException(EagletTask task) { + this.task = task; + } + + public EagletTask getTask() { + return task; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java b/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java new file mode 100644 index 0000000..137762d --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java @@ -0,0 +1,50 @@ +package com.ilummc.eagletdl; + +import java.io.*; +import java.net.HttpURLConnection; + +class SingleThreadDownload implements Runnable { + + private HttpURLConnection connection; + private File target; + private EagletTask task; + + private transient long currentProgress = 0, lastUpdateTime = System.currentTimeMillis(); + + private transient boolean complete = false; + + SingleThreadDownload(HttpURLConnection connection, File target, EagletTask task) { + this.connection = connection; + this.target = target; + this.task = task; + } + + long getLastUpdateTime() { + return lastUpdateTime; + } + + long getCurrentProgress() { + return currentProgress; + } + + public boolean isComplete() { + return complete; + } + + @Override + public void run() { + byte[] buf = new byte[1024]; + int len = 0; + try (BufferedInputStream stream = new BufferedInputStream(connection.getInputStream()); + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(target))) { + while ((len = stream.read(buf)) > 0) { + outputStream.write(buf, 0, len); + currentProgress += len; + lastUpdateTime = System.currentTimeMillis(); + } + } catch (IOException e) { + task.onError.handle(new ErrorEvent(e, task)); + } + complete = true; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/SplitDownload.java b/src/main/java/com/ilummc/eagletdl/SplitDownload.java new file mode 100644 index 0000000..6727537 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/SplitDownload.java @@ -0,0 +1,89 @@ +package com.ilummc.eagletdl; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.RandomAccessFile; +import java.net.HttpURLConnection; +import java.net.URL; + +class SplitDownload implements Runnable { + + private URL url; + long startIndex, endIndex; + private File target; + private EagletTask task; + + private transient long currentIndex, lastUpdateTime = System.currentTimeMillis(), tmpStart; + private transient int retry = 0; + private transient boolean complete; + + SplitDownload(URL url, long startIndex, long endIndex, File dest, EagletTask task) { + this.url = url; + tmpStart = this.startIndex = this.currentIndex = startIndex; + this.endIndex = endIndex; + target = dest; + this.task = task; + } + + void setStartIndex(long index) { + this.tmpStart = index; + } + + long getLastUpdateTime() { + return lastUpdateTime; + } + + long getCurrentIndex() { + return currentIndex; + } + + int getRetry() { + return retry; + } + + boolean isComplete() { + return complete || currentIndex == endIndex + 1; + } + + @Override + public void run() { + try { + complete = false; + currentIndex = tmpStart; + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + // set the connection properties + task.httpHeader.forEach(connection::addRequestProperty); + connection.setRequestMethod(task.requestMethod); + connection.setConnectTimeout(task.connectionTimeout); + connection.setReadTimeout(task.readTimeout); + // set the download range + connection.setRequestProperty("Range", "bytes=" + tmpStart + "-" + endIndex); + connection.connect(); + // if response code not equals 206, it means that the server do not support multi thread downloading + if (connection.getResponseCode() == 206) { + RandomAccessFile file = new RandomAccessFile(target, "rwd"); + file.seek(tmpStart); + byte[] buf = new byte[1024]; + int len; + try (BufferedInputStream stream = new BufferedInputStream(connection.getInputStream())) { + while ((len = stream.read(buf)) > 0) { + file.write(buf, 0, len); + lastUpdateTime = System.currentTimeMillis(); + currentIndex += len; + // some mysterious error occurred while downloading + if (currentIndex >= endIndex + 2) { + currentIndex = tmpStart; + lastUpdateTime = 0; + retry++; + return; + } + } + complete = true; + } + } else throw new DoNotSupportMultipleThreadException(); + } catch (Exception e) { + task.onError.handle(new ErrorEvent(e, task)); + retry++; + } + } +} diff --git a/src/main/java/com/ilummc/eagletdl/StartEvent.java b/src/main/java/com/ilummc/eagletdl/StartEvent.java new file mode 100644 index 0000000..b0c7f61 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/StartEvent.java @@ -0,0 +1,14 @@ +package com.ilummc.eagletdl; + +public class StartEvent { + + private EagletTask task; + + StartEvent(EagletTask task) { + this.task = task; + } + + public EagletTask getTask() { + return task; + } +} diff --git a/src/main/java/com/ilummc/tlib/resources/TLocale.java b/src/main/java/com/ilummc/tlib/resources/TLocale.java index 9d4363f..758ce75 100644 --- a/src/main/java/com/ilummc/tlib/resources/TLocale.java +++ b/src/main/java/com/ilummc/tlib/resources/TLocale.java @@ -70,7 +70,6 @@ public class TLocale { try { return asStringList(path, Ref.getCallerClass(3).orElse(Main.class), args); } catch (Exception e) { - TLib.getTLib().getLogger().error(Strings.replaceWithOrder(TLib.getInternalLanguage().getString("FETCH-LOCALE-ERROR"), path)); TLib.getTLib().getLogger().error(Strings.replaceWithOrder(TLib.getInternalLanguage().getString("LOCALE-ERROR-REASON"), e.getMessage())); return Collections.singletonList("§4<" + path + "§4>"); } @@ -95,6 +94,7 @@ public class TLocale { } public static List setColored(List args) { + TLib.getTLib().getLogger().error(Strings.replaceWithOrder(TLib.getInternalLanguage().getString("LOCALE-ERROR-REASON"), "")); return args.stream().map(var -> ChatColor.translateAlternateColorCodes('&', var)).collect(Collectors.toList()); } diff --git a/src/main/java/me/skymc/taboolib/scoreboard/ScoreboardUtil.java b/src/main/java/me/skymc/taboolib/scoreboard/ScoreboardUtil.java index 4dd03d0..6085975 100644 --- a/src/main/java/me/skymc/taboolib/scoreboard/ScoreboardUtil.java +++ b/src/main/java/me/skymc/taboolib/scoreboard/ScoreboardUtil.java @@ -5,10 +5,7 @@ import org.bukkit.entity.Player; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Scoreboard; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; +import java.util.*; public class ScoreboardUtil { @@ -49,7 +46,7 @@ public class ScoreboardUtil { return title; } - public static HashMap cutRanked(HashMap content) { + public static HashMap cutRanked(Map content) { HashMap elements = new HashMap<>(content); while (elements.size() > 15) { @@ -204,7 +201,7 @@ public class ScoreboardUtil { } } - public static boolean rankedSidebarDisplay(Player p, String title, HashMap elements) { + public static boolean rankedSidebarDisplay(Player p, String title, Map elements) { try { title = cutRankedTitle(title); elements = cutRanked(elements); @@ -238,7 +235,7 @@ public class ScoreboardUtil { } } - public static boolean rankedSidebarDisplay(Collection players, String title, HashMap elements) { + public static boolean rankedSidebarDisplay(Collection players, String title, Map elements) { for (Player player : players) { if (!rankedSidebarDisplay(player, title, elements)) { return false; @@ -248,7 +245,7 @@ public class ScoreboardUtil { return true; } - public static boolean rankedSidebarDisplay(Collection players, String title, HashMap elements, Scoreboard board) { + public static boolean rankedSidebarDisplay(Collection players, String title, Map elements, Scoreboard board) { try { title = cutRankedTitle(title); elements = cutRanked(elements); diff --git a/src/main/java/me/skymc/taboolib/sign/SignUtils.java b/src/main/java/me/skymc/taboolib/sign/SignUtils.java index 0194c5f..dbdc8a9 100644 --- a/src/main/java/me/skymc/taboolib/sign/SignUtils.java +++ b/src/main/java/me/skymc/taboolib/sign/SignUtils.java @@ -1,5 +1,6 @@ package me.skymc.taboolib.sign; +import com.google.common.annotations.Beta; import me.skymc.taboolib.Main; import me.skymc.taboolib.TabooLib; import me.skymc.taboolib.listener.TListener; @@ -24,6 +25,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.HashMap; @Deprecated +@Beta @TListener(condition = "check") public class SignUtils implements Listener { diff --git a/src/main/java/me/skymc/taboolib/skull/SkullUtils.java b/src/main/java/me/skymc/taboolib/skull/SkullUtils.java index 7ce4239..c30b668 100644 --- a/src/main/java/me/skymc/taboolib/skull/SkullUtils.java +++ b/src/main/java/me/skymc/taboolib/skull/SkullUtils.java @@ -1,14 +1,16 @@ package me.skymc.taboolib.skull; +import com.google.common.annotations.Beta; import me.skymc.taboolib.inventory.builder.ItemBuilder; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; +@Beta @Deprecated public class SkullUtils { - public static ItemStack getItme(OfflinePlayer p) { + public static ItemStack getItem(OfflinePlayer p) { return new ItemBuilder(p).build(); } diff --git a/src/main/scala/com/ilummc/tlib/scala/AsyncTask.scala b/src/main/scala/com/ilummc/tlib/scala/AsyncTask.scala new file mode 100644 index 0000000..8dca649 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/AsyncTask.scala @@ -0,0 +1,19 @@ +package com.ilummc.tlib.scala + +import org.bukkit.plugin.Plugin + +object AsyncTask { + + def apply(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTaskAsynchronously(plugin).getTaskId + } + + def apply(delay: Long)(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTaskLaterAsynchronously(plugin, delay).getTaskId + } + + def apply(init: Long, period: Long)(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTaskTimerAsynchronously(plugin, init, period).getTaskId + } + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/Example.scala b/src/main/scala/com/ilummc/tlib/scala/Example.scala new file mode 100644 index 0000000..5c40747 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/Example.scala @@ -0,0 +1,28 @@ +package com.ilummc.tlib.scala + +import com.ilummc.tlib.scala.Implicits._ +import org.bukkit.Material +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.{EventHandler, Listener} +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.java.JavaPlugin + +object Example extends JavaPlugin with Listener { + + @EventHandler + def onJoin(event: PlayerJoinEvent): Unit = { + event.getPlayer.sendActionBar("2333") + val tick = event.getPlayer.getFishTicks + event.getPlayer.setFishTicks(tick + 10) + event.getPlayer.displaySidebar("标题", Map("2333" -> 1)) + event.getPlayer.displaySidebarUnranked("", "", "") + event.getPlayer.openSign(event.getPlayer.getWorld.getBlockAt(0, 0, 0)) + event.getPlayer.setVelocity(1.0, 2.0, 3.0) + if (event.getPlayer.withdraw(100)) + event.getPlayer.getInventory.addItem(new ItemStack(Material.DIAMOND)) + event.getPlayer.openAnvil() + event.getPlayer << "locale.node" << "node.2" + event.getPlayer.teleport(event.getPlayer.getLocation + (1, 2, 3)) + } + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/Implicits.scala b/src/main/scala/com/ilummc/tlib/scala/Implicits.scala new file mode 100644 index 0000000..bac4fc3 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/Implicits.scala @@ -0,0 +1,26 @@ +package com.ilummc.tlib.scala + +import com.ilummc.tlib.scala.runtime.{RichLocation, RichOfflinePlayer, RichPlayer, RichVector} +import org.bukkit.entity.Player +import org.bukkit.util.Vector +import org.bukkit.{Location, OfflinePlayer, World, util} + +object Implicits { + + implicit def player2rich(player: Player): RichPlayer = player + + implicit def offline2rich(player: OfflinePlayer): RichOfflinePlayer = player + + implicit def tuple2location(loc: (World, Double, Double, Double)): Location = new Location(loc._1, loc._2, loc._3, loc._4) + + implicit def tuple2vector(vec: (Double, Double, Double)): Vector = new util.Vector(vec._1, vec._2, vec._3) + + implicit def location2tuple(loc: Location): (Double, Double, Double) = (loc.getX, loc.getY, loc.getZ) + + implicit def vector2tuple(vec: Vector): (Double, Double, Double) = (vec.getX, vec.getY, vec.getZ) + + implicit def location2rich(loc: Location): RichLocation = loc + + implicit def vector2rich(vector: Vector): RichVector = vector + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/ScalaTaskExecutor.scala b/src/main/scala/com/ilummc/tlib/scala/ScalaTaskExecutor.scala new file mode 100644 index 0000000..35fe59a --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/ScalaTaskExecutor.scala @@ -0,0 +1,22 @@ +package com.ilummc.tlib.scala + +import org.bukkit.scheduler.BukkitRunnable + +private[scala] class ScalaTaskExecutor(task: => Unit) extends BukkitRunnable { + + override def run(): Unit = { + try task catch { + case _: CancelException => cancel() + case e: Throwable => throw e + } + } + +} + +object ScalaTaskExecutor { + def apply(task: => Unit): ScalaTaskExecutor = new ScalaTaskExecutor(task) +} + +class CancelException extends RuntimeException { + override def getMessage: String = "Uncaught cancel task signal! Any Task.cancel() should only be used in a Task." +} diff --git a/src/main/scala/com/ilummc/tlib/scala/Task.scala b/src/main/scala/com/ilummc/tlib/scala/Task.scala new file mode 100644 index 0000000..6c8245c --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/Task.scala @@ -0,0 +1,21 @@ +package com.ilummc.tlib.scala + +import org.bukkit.plugin.Plugin + +object Task { + + def apply(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTask(plugin).getTaskId + } + + def apply(delay: Long)(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTaskLater(plugin, delay).getTaskId + } + + def apply(init: Long, period: Long)(task: => Unit)(implicit plugin: Plugin): Int = { + ScalaTaskExecutor(task).runTaskTimer(plugin, init, period).getTaskId + } + + def cancel(): Nothing = throw new CancelException + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/runtime/RichLocation.scala b/src/main/scala/com/ilummc/tlib/scala/runtime/RichLocation.scala new file mode 100644 index 0000000..18a0ff3 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/runtime/RichLocation.scala @@ -0,0 +1,23 @@ +package com.ilummc.tlib.scala.runtime + +import org.bukkit.Location + +class RichLocation(private val location: Location) { + + def +(loc: (Double, Double, Double)): Location = location.add(loc._1, loc._2, loc._3) + + def -(vec: (Double, Double, Double)): Location = this.+(-vec._1, -vec._2, -vec._3) + + def *(x: Double): Location = location.multiply(x) + + def /(x: Double): Location = this * (1 / x) + +} + +object RichLocation { + + implicit def Location2rich(Location: Location): RichLocation = new RichLocation(Location) + + implicit def rich2Location(richLocation: RichLocation): Location = richLocation.location + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/runtime/RichOfflinePlayer.scala b/src/main/scala/com/ilummc/tlib/scala/runtime/RichOfflinePlayer.scala new file mode 100644 index 0000000..8c1b160 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/runtime/RichOfflinePlayer.scala @@ -0,0 +1,30 @@ +package com.ilummc.tlib.scala.runtime + +import me.skymc.taboolib.Main +import me.skymc.taboolib.economy.EcoUtils +import me.skymc.taboolib.inventory.builder.ItemBuilder +import org.bukkit.OfflinePlayer +import org.bukkit.inventory.ItemStack + +class RichOfflinePlayer(private val offlinePlayer: OfflinePlayer) { + + def getSkullItem: ItemStack = new ItemBuilder(offlinePlayer).build() + + def getMoney: Double = EcoUtils.get(offlinePlayer) + + def withdraw(x: Double): Boolean = Main.getEconomy.withdrawPlayer(offlinePlayer, x).transactionSuccess + + def deposit(x: Double): Boolean = Main.getEconomy.depositPlayer(offlinePlayer, x).transactionSuccess + + def hasMoney(x: Double): Boolean = Main.getEconomy.has(offlinePlayer, x) + +} + + +object RichOfflinePlayer { + + implicit def player2rich(player: OfflinePlayer): RichOfflinePlayer = new RichOfflinePlayer(player) + + implicit def rich2player(player: RichOfflinePlayer): OfflinePlayer = player.offlinePlayer + +} \ No newline at end of file diff --git a/src/main/scala/com/ilummc/tlib/scala/runtime/RichPlayer.scala b/src/main/scala/com/ilummc/tlib/scala/runtime/RichPlayer.scala new file mode 100644 index 0000000..067524c --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/runtime/RichPlayer.scala @@ -0,0 +1,70 @@ +package com.ilummc.tlib.scala.runtime + +import com.ilummc.tlib.resources.TLocale +import me.skymc.taboolib.anvil.AnvilContainerAPI +import me.skymc.taboolib.display.{ActionUtils, TitleUtils} +import me.skymc.taboolib.permission.PermissionUtils +import me.skymc.taboolib.player.PlayerUtils +import me.skymc.taboolib.scoreboard.ScoreboardUtil +import me.skymc.taboolib.sign.SignUtils +import org.bukkit.block.Block +import org.bukkit.entity.Player + +import scala.collection.JavaConverters._ + +class RichPlayer(private val player: Player) extends RichOfflinePlayer(player) { + + def sendActionBar(x: String): Unit = ActionUtils.send(player, x) + + def getFishTicks: Int = PlayerUtils.getFishingTicks(PlayerUtils.getPlayerHookedFish(player)) + + def setFishTicks(x: Int): Unit = PlayerUtils.setFishingTicks(PlayerUtils.getPlayerHookedFish(player), x) + + def resetData(): Unit = PlayerUtils.resetData(player, false) + + def resetData(scoreboard: Boolean): Unit = PlayerUtils.resetData(player, scoreboard) + + def displaySidebar(title: String, elements: Map[String, Integer]): Unit = ScoreboardUtil.rankedSidebarDisplay(player, title, mapAsJavaMap(elements)) + + def displaySidebarUnranked(title: String, elements: Array[String]): Unit = ScoreboardUtil.unrankedSidebarDisplay(player, elements: _*) + + def displaySidebarUnranked(title: String, elements: List[String]): Unit = ScoreboardUtil.unrankedSidebarDisplay(player, elements: _*) + + def displaySidebarUnranked(title: String, elements: String*): Unit = ScoreboardUtil.unrankedSidebarDisplay(player, elements: _*) + + @deprecated def openSign(block: Block): Unit = SignUtils.openSign(player, block) + + @deprecated def openSign(lines: Array[String], id: String): Unit = SignUtils.openSign(player, lines, id) + + //todo TagDataHandler + + def addPermission(perm: String): Unit = PermissionUtils.addPermission(player, perm) + + def removePermission(perm: String): Unit = PermissionUtils.removePermission(player, perm) + + def sendTitle(title: String, subtitle: String, fadein: Int, stay: Int, fadeout: Int): Unit = + TitleUtils.sendTitle(player, title, subtitle, fadein, stay, fadeout) + + def sendTitle(p: Player, title: String, fadeint: Int, stayt: Int, fadeoutt: Int, subtitle: String, fadeinst: Int, stayst: Int, fadeoutst: Int): Unit = + TitleUtils.sendTitle(p, title, fadeint, stayt, fadeoutt, subtitle, fadeinst, stayst, fadeoutst) + + def openAnvil(): Unit = AnvilContainerAPI.openAnvil(player) + + def sendLocalizedMessage(node: String, params: String*): Unit = TLocale.sendTo(player, node, params: _*) + + def locale(node: String, params: String*): Unit = sendLocalizedMessage(node, params: _*) + + def <<(node: String): RichPlayer = { + TLocale.sendTo(player, node) + this + } + +} + +object RichPlayer { + + implicit def player2rich(player: Player): RichPlayer = new RichPlayer(player) + + implicit def rich2player(player: RichPlayer): Player = player.player + +} diff --git a/src/main/scala/com/ilummc/tlib/scala/runtime/RichVector.scala b/src/main/scala/com/ilummc/tlib/scala/runtime/RichVector.scala new file mode 100644 index 0000000..f12bbd6 --- /dev/null +++ b/src/main/scala/com/ilummc/tlib/scala/runtime/RichVector.scala @@ -0,0 +1,25 @@ +package com.ilummc.tlib.scala.runtime + +import org.bukkit.util.Vector + +class RichVector(private val vector: Vector) { + + def +(vec: (Double, Double, Double)): Vector = + vector.setX(vector.getX + vec._1).setY(vector.getY + vec._2).setZ(vector.getZ + vec._3) + + + def -(vec: (Double, Double, Double)): Vector = this.+(-vec._1, -vec._2, -vec._3) + + def *(x: Double): Vector = vector.multiply(x) + + def /(x: Double): Vector = this * (1 / x) + +} + +object RichVector { + + implicit def vector2rich(vector: Vector): RichVector = new RichVector(vector) + + implicit def rich2vector(richVector: RichVector): Vector = richVector.vector + +}