commit 1919e6035a7c10e185faa9dc6c1d3a8f95c33a59 Author: zhangjiayu Date: Mon Dec 20 17:08:21 2021 +0800 Update:合并项目代码成新项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c0e049 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..89731f5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + cn.montaro + aria2-client + 1.0-SNAPSHOT + + + 8 + 8 + + + + + + junit + junit + 4.13.2 + test + + + + org.projectlombok + lombok + 1.18.20 + + + + + com.google.code.gson + gson + 2.8.9 + + + + org.java-websocket + Java-WebSocket + 1.5.2 + + + + + \ No newline at end of file diff --git a/src/main/java/cn/montaro/aria2/api/Aria2Client.java b/src/main/java/cn/montaro/aria2/api/Aria2Client.java new file mode 100644 index 0000000..b0c315d --- /dev/null +++ b/src/main/java/cn/montaro/aria2/api/Aria2Client.java @@ -0,0 +1,58 @@ +package cn.montaro.aria2.api; + +import java.util.List; +import java.util.Map; + +/** + * Description: + * Aria2 Client Api + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +public interface Aria2Client { + + /** + * 添加文件下载 + * + * @param uris + * @param option + * @param position + * @return + */ + String addUri(List uris, Map option, Integer position); + + /** + * 查询支持的方法列表 + * + * @return 方法列表 + */ + List listMethods(); + + /** + * 移除一个下载任务,如果任务正在进行中,任务会先停止 + * + * @param gid GID + * @return 操作成功返回GID + */ + String remove(String gid); + + String forceRemove(String gid); + + String pause(String gid); + + String forcePause(String gid); + + String pauseAll(); + + String forcePauseAll(); + + String unpause(String gid); + + void tellStatus(String gid); + + void tellStopped(Integer offset, Integer num, String[] keys); + + void changePosition(String gid, Integer pos, String how); + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketClient.java b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketClient.java new file mode 100644 index 0000000..e4c46df --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketClient.java @@ -0,0 +1,295 @@ +package cn.montaro.aria2.client.websocket; + + +import cn.montaro.aria2.api.Aria2Client; +import cn.montaro.aria2.client.websocket.constants.Aria2Method; +import cn.montaro.aria2.client.websocket.exception.Aria2WebSocketClientConnectTimeoutException; +import cn.montaro.aria2.client.websocket.exception.Aria2WebSocketClientException; +import cn.montaro.aria2.client.websocket.exception.Aria2WebSocketClientTimeoutException; +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.java_websocket.handshake.ServerHandshake; + +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/15 + */ +@Slf4j +public class Aria2WebSocketClient extends WebSocketClient implements Aria2Client { + + private Gson gson = null; + private Aria2WebSocketConfig config = null; + private final Map resultValueMap = new ConcurrentHashMap<>(); + private final Map resultTypeMap = new ConcurrentHashMap<>(); + private final Map resultExceptionMap = new ConcurrentHashMap<>(); + + @SneakyThrows + public Aria2WebSocketClient(Aria2WebSocketConfig config) { + super(config.getURI()); + this.connectBlocking(config.getTimeout(), TimeUnit.MILLISECONDS); + this.config = config; + this.gson = new GsonBuilder().create(); + if (!this.isOpen()) { + throw new Aria2WebSocketClientConnectTimeoutException(config.getURI(), config.getTimeout()); + } + } + + private JsonElement getJsonElement(Object... val) { + ArrayList params = new ArrayList<>(Arrays.asList(val)); + String secret = "token:"; + if (config.getSecret() != null) { + secret += config.getSecret(); + } + params.add(0, secret); + int size = params.size(); + ListIterator listIterator = params.listIterator(size); + while (listIterator.hasPrevious()) { + Object previous = listIterator.previous(); + if (previous == null) { + listIterator.remove(); + } else { + break; + } + } + return gson.toJsonTree(params); + } + + /** + * 通过参数构建请求 + * + * @param method Aria2调用方法 + * @param resultType 返回结果类型 + * @return 请求 + */ + private Aria2WebSocketRequest buildRequest(String method, Type resultType, Object... params) { + Aria2WebSocketRequest request = new Aria2WebSocketRequest(); + String id = UUID.randomUUID().toString(); + request.setId(id); + request.setMethod(method); + request.setParams(this.getJsonElement(params)); + this.saveMap(id, resultType); + return request; + } + + /** + * 序列化请求为Json格式 + * + * @param request 请求参数 + * @return 序列化成Json的请求内容 + */ + private String serialize(Aria2WebSocketRequest request) { + return this.gson.toJson(request); + } + + /** + * 保存id关系映射结果 + * + * @param id id + * @param resultType 结果结果类型 + */ + private void saveMap(String id, Type resultType) { + // this.resultValueMap.put(id, null); + this.resultTypeMap.put(id, resultType); + // this.resultExceptionMap.put(id, null); + } + + /** + * 发送请求 + * + * @param request 请求 + */ + private void sendRequest(Aria2WebSocketRequest request) { + String body = this.serialize(request); + log.debug("Send Request:{}", body); + try { + this.send(body); + } catch (WebsocketNotConnectedException e) { + + } + } + + /** + * 等待结果返回 + * + * @param id id + * @param 返回结果类型 + * @return + */ + @SneakyThrows + private T waitResult(String id) { + Aria2WebSocketResponse result = null; + Date startTime = new Date(); + while ((result = this.resultValueMap.get(id)) == null) { + Aria2WebSocketClientException exception = this.resultExceptionMap.get(id); + if (exception != null) { + this.clearMap(id); + throw exception; + } + + boolean isStop = (new Date().getTime() - startTime.getTime()) >= this.config.getTimeout(); + if (isStop) { + throw new Aria2WebSocketClientTimeoutException(); + } + } + this.clearMap(id); + return result.getResult(); + } + + /** + * 清理id映射关系 + * + * @param id id + */ + private void clearMap(String id) { + this.resultValueMap.remove(id); + this.resultTypeMap.remove(id); + this.resultExceptionMap.remove(id); + } + + public String addUri(String[] uris) { + List uriList = Arrays.asList(uris); + return this.addUri(uriList, null, null); + } + + @Override + public String addUri(List uris, Map option, Integer position) { + Aria2WebSocketRequest request = this.buildRequest( + Aria2Method.ADD_URI, + new TypeToken>() { + }.getType(), + uris, + option, + position + ); + String id = request.getId(); + this.sendRequest(request); + return this.waitResult(id); + } + + + @Override + public List listMethods() { + Aria2WebSocketRequest request = this.buildRequest( + Aria2Method.LIST_METHODS, + null, + new TypeToken>>() { + }.getType() + ); + String id = request.getId(); + this.sendRequest(request); + return this.waitResult(id); + } + + @Override + public String remove(String s) { + return null; + } + + @Override + public String forceRemove(String s) { + return null; + } + + @Override + public String pause(String s) { + return null; + } + + @Override + public String forcePause(String s) { + return null; + } + + @Override + public String pauseAll() { + return null; + } + + @Override + public String forcePauseAll() { + return null; + } + + @Override + public String unpause(String s) { + return null; + } + + @Override + public void tellStatus(String gid) { + + } + + // --------------WebSocket Client-------------- + + @Override + public void tellStopped(Integer offset, Integer num, String[] keys) { + + } + + @Override + public void changePosition(String gid, Integer pos, String how) { + + } + + + @Override + public void onOpen(ServerHandshake serverHandshake) { + + } + + @Override + public void onMessage(String message) { + log.debug("onMessage : {}", message); + JsonElement jsonElement = JsonParser.parseString(message); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + String id = jsonObject.get("id").getAsString(); + if (id == null) { + return; + } + + try { + JsonObject error = jsonObject.getAsJsonObject("error"); + if (error != null) { + System.out.println("put exception"); + String errorMessage = error.get("message").getAsString(); + this.resultExceptionMap.put(id, new Aria2WebSocketClientException(errorMessage)); + return; + } + + Type resultType = this.resultTypeMap.get(id); + Aria2WebSocketResponse result = null; + try { + result = gson.fromJson(jsonElement, resultType); + } catch (Exception e) { + this.resultExceptionMap.put(id, new Aria2WebSocketClientException(e)); + } + this.resultValueMap.put(id, result); + } catch (Exception e) { + this.resultExceptionMap.put(id, new Aria2WebSocketClientException(e)); + } + } + + @Override + @SneakyThrows + public void onClose(int code, String reason, boolean remote) { + this.reconnectBlocking(); + } + + @Override + public void onError(Exception ex) { + + } +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketConfig.java b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketConfig.java new file mode 100644 index 0000000..94fa84b --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketConfig.java @@ -0,0 +1,49 @@ +package cn.montaro.aria2.client.websocket; + +import cn.montaro.aria2.client.websocket.constants.WebSocketProtocol; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.net.URI; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/15 + */ +@Data +@Accessors(chain = true) +public class Aria2WebSocketConfig { + + /** + * 服务器地址 默认localhost + */ + private String host = "localhost"; + /** + * RPC连接端口 默认6800 + */ + private Integer port = 6800; + /** + * jsonrpc路径 默认jsonrpc + */ + private String path = "jsonrpc"; + /** + * 连接密钥 通过rpc-secret设置 + */ + private String secret; + /** + * 超时时间 单位ms + */ + private Long timeout = 10000L; + /** + * 连接协议 默认ws + * @see WebSocketProtocol + */ + private String protocol = WebSocketProtocol.PROTOCOL_WS; + + public URI getURI() { + return URI.create(protocol + "://" + host + ":" + port + "/" + path); + } + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketRequest.java b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketRequest.java new file mode 100644 index 0000000..6877cdf --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketRequest.java @@ -0,0 +1,27 @@ +package cn.montaro.aria2.client.websocket; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +@Data +@Accessors(chain = true) +public class Aria2WebSocketRequest implements Serializable { + + private String id; + private String jsonrpc = "2.0"; + private String method; + private JsonElement params; + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketResponse.java b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketResponse.java new file mode 100644 index 0000000..6217467 --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/Aria2WebSocketResponse.java @@ -0,0 +1,18 @@ +package cn.montaro.aria2.client.websocket; + +import lombok.Data; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +@Data +public class Aria2WebSocketResponse { + + private String id; + private String jsonrpc; + private T result; + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/constants/Aria2Method.java b/src/main/java/cn/montaro/aria2/client/websocket/constants/Aria2Method.java new file mode 100644 index 0000000..5f0a50a --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/constants/Aria2Method.java @@ -0,0 +1,59 @@ +package cn.montaro.aria2.client.websocket.constants; + +/** + * Description: + * Aria2可调用方法名称 + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +public class Aria2Method { + + private final static String ARIA2 = "aria2."; + private final static String SYSTEM = "system."; + + public final static String ADD_URI = ARIA2 + "addUri"; + public final static String ADD_TORRENT = ARIA2 + "addTorrent"; + public final static String ADD_METALINK = ARIA2 + "addMetalink"; + + public final static String REMOVE = ARIA2 + "remove"; + public final static String FORCE_REMOVE = ARIA2 + "forceRemove"; + + public final static String PAUSE = ARIA2 + "pause"; + public final static String UNPAUSE = ARIA2 + "unpause"; + public final static String PAUSE_ALL = ARIA2 + "pauseAll"; + public final static String UNPAUSE_ALL = ARIA2 + "unpauseAll"; + public final static String FORCE_PAUSE = ARIA2 + "forcePause"; + public final static String FORCE_PAUSE_ALL = ARIA2 + "forcePauseAll"; + + public final static String TELL_STATUS = ARIA2 + "tellStatus"; + public final static String TELL_ACTIVE = ARIA2 + "tellActive"; + public final static String TELL_WAITING = ARIA2 + "tellWaiting"; + public final static String TELL_STOPPED = ARIA2 + "tellStopped"; + + public final static String GET_URIS = ARIA2 + "getUris"; + public final static String GET_FILES = ARIA2 + "getFiles"; + public final static String GET_PEERS = ARIA2 + "getPeers"; + public final static String GET_OPTION = ARIA2 + "getOption"; + public final static String GET_SERVERS = ARIA2 + "getServers"; + public final static String GET_VERSION = ARIA2 + "getVersion"; + public final static String GET_GLOBAL_STAT = ARIA2 + "getGlobalStat"; + public final static String GET_SESSION_INFO = ARIA2 + "getSessionInfo"; + + public final static String CHANGE_URI = ARIA2 + "changeUri"; + public final static String CHANGE_OPTION = ARIA2 + "changeOption"; + public final static String CHANGE_POSITION = ARIA2 + "changePosition"; + public final static String CHANGE_GLOBAL_OPTION = ARIA2 + "changeGlobalOption"; + + public final static String PURGE_DOWNLOAD_RESULT = ARIA2 + "purgeDownloadResult"; + public final static String REMOVE_DOWNLOAD_RESULT = ARIA2 + "removeDownloadResult"; + + public final static String SHUTDOWN = ARIA2 + "shutdown"; + public final static String SAVE_SESSION = ARIA2 + "saveSession"; + public final static String FORCE_SHUTDOWN = ARIA2 + "forceShutdown"; + + public final static String MULTICALL = SYSTEM + "multicall"; + public final static String LIST_METHODS = SYSTEM + "listMethods"; + public final static String LIST_NOTIFICATIONS = SYSTEM + "listNotifications"; + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/constants/WebSocketProtocol.java b/src/main/java/cn/montaro/aria2/client/websocket/constants/WebSocketProtocol.java new file mode 100644 index 0000000..f1086dd --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/constants/WebSocketProtocol.java @@ -0,0 +1,14 @@ +package cn.montaro.aria2.client.websocket.constants; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/15 + */ +public class WebSocketProtocol { + + public final static String PROTOCOL_WS = "ws"; + public final static String PROTOCOL_WSS = "wss"; + +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientConnectTimeoutException.java b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientConnectTimeoutException.java new file mode 100644 index 0000000..9df96cc --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientConnectTimeoutException.java @@ -0,0 +1,29 @@ +package cn.montaro.aria2.client.websocket.exception; + +import java.net.URI; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/16 + */ +public class Aria2WebSocketClientConnectTimeoutException extends Aria2WebSocketClientException { + + private long timeout; + + private URI uri; + + public Aria2WebSocketClientConnectTimeoutException() { + } + + @Override + public String getMessage() { + return String.format("Websocket connect to %s timeout:%dms", uri.toString(), timeout); + } + + public Aria2WebSocketClientConnectTimeoutException(URI uri, long timeout) { + this.uri = uri; + this.timeout = timeout; + } +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientException.java b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientException.java new file mode 100644 index 0000000..292b891 --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientException.java @@ -0,0 +1,21 @@ +package cn.montaro.aria2.client.websocket.exception; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/16 + */ +public class Aria2WebSocketClientException extends RuntimeException { + + public Aria2WebSocketClientException() { + } + + public Aria2WebSocketClientException(String message) { + super(message); + } + + public Aria2WebSocketClientException(Exception e) { + super(e); + } +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientTimeoutException.java b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientTimeoutException.java new file mode 100644 index 0000000..5eedd9d --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/exception/Aria2WebSocketClientTimeoutException.java @@ -0,0 +1,17 @@ +package cn.montaro.aria2.client.websocket.exception; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/16 + */ +public class Aria2WebSocketClientTimeoutException extends Aria2WebSocketClientException { + public Aria2WebSocketClientTimeoutException() { + this("Waiting for the result timed out"); + } + + public Aria2WebSocketClientTimeoutException(String message) { + super(message); + } +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/param/AddUriParam.java b/src/main/java/cn/montaro/aria2/client/websocket/param/AddUriParam.java new file mode 100644 index 0000000..d778351 --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/param/AddUriParam.java @@ -0,0 +1,57 @@ +package cn.montaro.aria2.client.websocket.param; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AddUriParam extends Aria2Param { + + /** + * 资源URI,支持HTTP/FTP/SFTP/BitTorrent + */ + private List uris = new ArrayList<>(); + /** + * 下载选项 详情请看文档 + */ + private Map options = null; + /** + * 下载顺序,如果是0则添加到开头 + *

If position is given, it must be an integer starting from 0. The new download will be inserted at position in the waiting queue.

+ *

If position is omitted or position is larger than the current size of the queue, the new download is appended to the end of the queue.

+ */ + private Integer position; + + public AddUriParam addUri(String uri) { + this.uris.add(uri); + return this; + } + + public AddUriParam setOption(String name, String value) { + if (this.options == null) { + this.options = new HashMap<>(); + } + this.options.put(name, value); + return this; + } + + public void setPosition(Integer position) { + if (this.options == null) { + this.options = new HashMap<>(); + } + this.position = position; + } +} diff --git a/src/main/java/cn/montaro/aria2/client/websocket/param/Aria2Param.java b/src/main/java/cn/montaro/aria2/client/websocket/param/Aria2Param.java new file mode 100644 index 0000000..a830e1b --- /dev/null +++ b/src/main/java/cn/montaro/aria2/client/websocket/param/Aria2Param.java @@ -0,0 +1,16 @@ +package cn.montaro.aria2.client.websocket.param; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/14 + */ +@Data +public class Aria2Param implements Serializable { + +} diff --git a/src/test/java/ClientTest.java b/src/test/java/ClientTest.java new file mode 100644 index 0000000..df34c1a --- /dev/null +++ b/src/test/java/ClientTest.java @@ -0,0 +1,22 @@ +import cn.montaro.aria2.client.websocket.Aria2WebSocketConfig; +import cn.montaro.aria2.client.websocket.Aria2WebSocketClient; +import org.junit.Test; + +/** + * Description: + * + * @author ZhangJiaYu + * @date 2021/12/15 + */ +public class ClientTest { + + @Test + public void test() { + Aria2WebSocketConfig aria2WebSocketConfig = new Aria2WebSocketConfig(); + aria2WebSocketConfig.setSecret("123456"); + Aria2WebSocketClient client = new Aria2WebSocketClient(aria2WebSocketConfig); + String gid = client.addUri(new String[]{"https://www.baidu.com"}); + System.out.println("gid = " + gid); + + } +}