response;
169 | try {
170 | response = this.httpClient.send(request, BodyHandlers.ofString());
171 | } catch (InterruptedException e) {
172 | throw new RuntimeException(e);
173 | }
174 | return wrapResponse(response, clazz, constructor);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/core/src/main/java/org/mineskin/ClientBuilder.java:
--------------------------------------------------------------------------------
1 | package org.mineskin;
2 |
3 | import com.google.gson.Gson;
4 | import org.mineskin.options.*;
5 | import org.mineskin.request.RequestHandler;
6 | import org.mineskin.request.RequestHandlerConstructor;
7 |
8 | import java.util.concurrent.Executor;
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.logging.Level;
12 |
13 | public class ClientBuilder {
14 |
15 | private static final int DEFAULT_GENERATE_QUEUE_INTERVAL = 200;
16 | private static final int DEFAULT_GENERATE_QUEUE_CONCURRENCY = 1;
17 | private static final int DEFAULT_GET_QUEUE_INTERVAL = 100;
18 | private static final int DEFAULT_GET_QUEUE_CONCURRENCY = 5;
19 | private static final int DEFAULT_JOB_CHECK_INTERVAL = 1000;
20 | private static final int DEFAULT_JOB_CHECK_INITIAL_DELAY = 2000;
21 | private static final int DEFAULT_JOB_CHECK_MAX_ATTEMPTS = 10;
22 |
23 | private String baseUrl = "https://api.mineskin.org";
24 | private String userAgent = "MineSkinClient";
25 | private String apiKey = null;
26 | private int timeout = 10000;
27 | private Gson gson = new Gson();
28 | private Executor getExecutor = null;
29 | private Executor generateExecutor = null;
30 | private IQueueOptions generateQueueOptions = null;
31 | private IQueueOptions getQueueOptions = null;
32 | private IJobCheckOptions jobCheckOptions = null;
33 | private RequestHandlerConstructor requestHandlerConstructor = null;
34 |
35 | private ClientBuilder() {
36 | }
37 |
38 | /**
39 | * Create a new ClientBuilder
40 | */
41 | public static ClientBuilder create() {
42 | return new ClientBuilder();
43 | }
44 |
45 | /**
46 | * Set the base URL for the API
47 | *
48 | * @param baseUrl the base URL, e.g. "https://api.mineskin.org"
49 | */
50 | public ClientBuilder baseUrl(String baseUrl) {
51 | this.baseUrl = baseUrl;
52 | return this;
53 | }
54 |
55 | /**
56 | * Set the User-Agent
57 | */
58 | public ClientBuilder userAgent(String userAgent) {
59 | this.userAgent = userAgent;
60 | return this;
61 | }
62 |
63 | /**
64 | * Set the API key
65 | */
66 | public ClientBuilder apiKey(String apiKey) {
67 | this.apiKey = apiKey;
68 | return this;
69 | }
70 |
71 | /**
72 | * Set the timeout
73 | */
74 | public ClientBuilder timeout(int timeout) {
75 | this.timeout = timeout;
76 | return this;
77 | }
78 |
79 | /**
80 | * Set the Gson instance
81 | */
82 | public ClientBuilder gson(Gson gson) {
83 | this.gson = gson;
84 | return this;
85 | }
86 |
87 | /**
88 | * Set the Executor for get requests
89 | */
90 | public ClientBuilder getExecutor(Executor getExecutor) {
91 | this.getExecutor = getExecutor;
92 | return this;
93 | }
94 |
95 | /**
96 | * Set the Executor for generate requests
97 | */
98 | public ClientBuilder generateExecutor(Executor generateExecutor) {
99 | this.generateExecutor = generateExecutor;
100 | return this;
101 | }
102 |
103 | /**
104 | * Set the ScheduledExecutorService for submitting queue jobs
105 | *
106 | * @deprecated use {@link #generateQueueOptions(IQueueOptions)} instead
107 | */
108 | @Deprecated
109 | public ClientBuilder generateRequestScheduler(ScheduledExecutorService scheduledExecutor) {
110 | this.generateQueueOptions = new QueueOptions(scheduledExecutor, DEFAULT_GENERATE_QUEUE_INTERVAL, DEFAULT_GENERATE_QUEUE_CONCURRENCY);
111 | return this;
112 | }
113 |
114 | /**
115 | * Set the options for submitting queue jobs
116 | * defaults to 200ms interval and 1 concurrent request
117 | * For example:
118 | *
119 | * {@code
120 | * GenerateQueueOptions.create()
121 | * .withInterval(200, TimeUnit.MILLISECONDS)
122 | * .withConcurrency(2)
123 | * }
124 | *
125 | *
126 | * @see GenerateQueueOptions
127 | * @see QueueOptions
128 | */
129 | public ClientBuilder generateQueueOptions(IQueueOptions queueOptions) {
130 | this.generateQueueOptions = queueOptions;
131 | return this;
132 | }
133 |
134 | /**
135 | * Set the ScheduledExecutorService for get requests, e.g. getting skins
136 | *
137 | * @deprecated use {@link #getQueueOptions(IQueueOptions)} instead
138 | */
139 | @Deprecated
140 | public ClientBuilder getRequestScheduler(ScheduledExecutorService scheduledExecutor) {
141 | this.getQueueOptions = new QueueOptions(scheduledExecutor, DEFAULT_GET_QUEUE_INTERVAL, DEFAULT_GET_QUEUE_CONCURRENCY);
142 | return this;
143 | }
144 |
145 | /**
146 | * Set the options for get requests, e.g. getting skins
147 | * defaults to 100ms interval and 5 concurrent requests
148 | * For example:
149 | *
150 | * {@code
151 | * GetQueueOptions.create()
152 | * .withInterval(500, TimeUnit.MILLISECONDS)
153 | * }
154 | *
155 | *
156 | * @see GetQueueOptions
157 | * @see QueueOptions
158 | */
159 | public ClientBuilder getQueueOptions(IQueueOptions queueOptions) {
160 | this.getQueueOptions = queueOptions;
161 | return this;
162 | }
163 |
164 | /**
165 | * Set the ScheduledExecutorService for checking job status
166 | *
167 | * @deprecated use {@link #jobCheckOptions(IJobCheckOptions)} instead
168 | */
169 | @Deprecated
170 | public ClientBuilder jobCheckScheduler(ScheduledExecutorService scheduledExecutor) {
171 | this.jobCheckOptions = new JobCheckOptions(scheduledExecutor, DEFAULT_JOB_CHECK_INTERVAL, DEFAULT_JOB_CHECK_INITIAL_DELAY, DEFAULT_JOB_CHECK_MAX_ATTEMPTS, false);
172 | return this;
173 | }
174 |
175 | /**
176 | * Set the options for checking job status
177 | * defaults to 1000ms interval, 2000ms initial delay, and 10 max attempts
178 | * For example:
179 | *
180 | * {@code
181 | * JobCheckOptions.create()
182 | * .withInitialDelay(1000, TimeUnit.MILLISECONDS)
183 | * .withInterval(RequestInterval.exponential())
184 | * .withMaxAttempts(50)
185 | * }
186 | *
187 | *
188 | * @see JobCheckOptions
189 | */
190 | public ClientBuilder jobCheckOptions(IJobCheckOptions jobCheckOptions) {
191 | this.jobCheckOptions = jobCheckOptions;
192 | return this;
193 | }
194 |
195 | /**
196 | * Set the constructor for the RequestHandler
197 | */
198 | public ClientBuilder requestHandler(RequestHandlerConstructor requestHandlerConstructor) {
199 | this.requestHandlerConstructor = requestHandlerConstructor;
200 | return this;
201 | }
202 |
203 | /**
204 | * Build the MineSkinClient
205 | */
206 | public MineSkinClient build() {
207 | if (requestHandlerConstructor == null) {
208 | throw new IllegalStateException("RequestHandlerConstructor is not set");
209 | }
210 | if ("MineSkinClient".equals(userAgent)) {
211 | MineSkinClientImpl.LOGGER.log(Level.WARNING, "Using default User-Agent: MineSkinClient - Please set a custom User-Agent (e.g. AppName/Version) to identify your application");
212 | }
213 | if (apiKey == null || apiKey.isBlank()) {
214 | apiKey = null;
215 | MineSkinClientImpl.LOGGER.log(Level.WARNING, "Creating MineSkinClient without API key - Please get an API key from https://account.mineskin.org/keys");
216 | } else if (apiKey.startsWith("msk_")) {
217 | String[] split = apiKey.split("_", 3);
218 | if (split.length == 3) {
219 | String id = split[1];
220 | MineSkinClientImpl.LOGGER.log(Level.FINE, "Creating MineSkinClient with API key: " + id);
221 | }
222 | }
223 |
224 | if (getExecutor == null) {
225 | getExecutor = Executors.newSingleThreadExecutor(r -> {
226 | Thread thread = new Thread(r);
227 | thread.setName("MineSkinClient/get");
228 | return thread;
229 | });
230 | }
231 | if (generateExecutor == null) {
232 | generateExecutor = Executors.newSingleThreadExecutor(r -> {
233 | Thread thread = new Thread(r);
234 | thread.setName("MineSkinClient/generate");
235 | return thread;
236 | });
237 | }
238 |
239 | if (generateQueueOptions == null) {
240 | generateQueueOptions = new QueueOptions(
241 | Executors.newSingleThreadScheduledExecutor(r -> {
242 | Thread thread = new Thread(r);
243 | thread.setName("MineSkinClient/scheduler");
244 | return thread;
245 | }),
246 | DEFAULT_GENERATE_QUEUE_INTERVAL,
247 | DEFAULT_GENERATE_QUEUE_CONCURRENCY
248 | );
249 | }
250 | if (getQueueOptions == null) {
251 | getQueueOptions = new QueueOptions(
252 | generateQueueOptions.scheduler(),
253 | DEFAULT_GET_QUEUE_INTERVAL,
254 | DEFAULT_GET_QUEUE_CONCURRENCY
255 | );
256 | }
257 | if (jobCheckOptions == null) {
258 | jobCheckOptions = new JobCheckOptions(
259 | generateQueueOptions.scheduler(),
260 | DEFAULT_JOB_CHECK_INTERVAL,
261 | DEFAULT_JOB_CHECK_INITIAL_DELAY,
262 | DEFAULT_JOB_CHECK_MAX_ATTEMPTS,
263 | false
264 | );
265 | }
266 |
267 | RequestHandler requestHandler = requestHandlerConstructor.construct(baseUrl, userAgent, apiKey, timeout, gson);
268 | RequestExecutors executors = new RequestExecutors(getExecutor, generateExecutor, generateQueueOptions, getQueueOptions, jobCheckOptions);
269 | MineSkinClientImpl client = new MineSkinClientImpl(requestHandler, executors);
270 | if (executors.generateQueueOptions() instanceof AutoGenerateQueueOptions autoGenerate) {
271 | autoGenerate.setClient(client);
272 | }
273 | return client;
274 | }
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/core/src/main/java/org/mineskin/MineSkinClientImpl.java:
--------------------------------------------------------------------------------
1 | package org.mineskin;
2 |
3 | import com.google.gson.JsonObject;
4 | import org.mineskin.data.*;
5 | import org.mineskin.exception.MineSkinRequestException;
6 | import org.mineskin.exception.MineskinException;
7 | import org.mineskin.options.IJobCheckOptions;
8 | import org.mineskin.request.*;
9 | import org.mineskin.request.source.UploadSource;
10 | import org.mineskin.response.*;
11 |
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.net.URL;
15 | import java.util.Map;
16 | import java.util.UUID;
17 | import java.util.concurrent.CompletableFuture;
18 | import java.util.logging.Level;
19 | import java.util.logging.Logger;
20 |
21 | import static com.google.common.base.Preconditions.checkNotNull;
22 |
23 | public class MineSkinClientImpl implements MineSkinClient {
24 |
25 | public static final Logger LOGGER = Logger.getLogger(MineSkinClient.class.getName());
26 |
27 | private final RequestExecutors executors;
28 |
29 | private final RequestHandler requestHandler;
30 | private final RequestQueue generateQueue;
31 | private final RequestQueue getQueue;
32 |
33 | private final QueueClient queueClient = new QueueClientImpl();
34 | private final GenerateClient generateClient = new GenerateClientImpl();
35 | private final SkinsClient skinsClient = new SkinsClientImpl();
36 | private final MiscClient miscClient = new MiscClientImpl();
37 |
38 | public MineSkinClientImpl(RequestHandler requestHandler, RequestExecutors executors) {
39 | this.requestHandler = checkNotNull(requestHandler);
40 | this.executors = checkNotNull(executors);
41 |
42 | this.generateQueue = new RequestQueue(executors.generateQueueOptions());
43 | this.getQueue = new RequestQueue(executors.getQueueOptions());
44 | }
45 |
46 | @Override
47 | public Logger getLogger() {
48 | return LOGGER;
49 | }
50 |
51 | /// //
52 |
53 |
54 | @Override
55 | public QueueClient queue() {
56 | return queueClient;
57 | }
58 |
59 | @Override
60 | public GenerateClient generate() {
61 | return generateClient;
62 | }
63 |
64 | @Override
65 | public SkinsClient skins() {
66 | return skinsClient;
67 | }
68 |
69 | @Override
70 | public MiscClient misc() {
71 | return miscClient;
72 | }
73 |
74 | class QueueClientImpl implements QueueClient {
75 |
76 | @Override
77 | public CompletableFuture submit(GenerateRequest request) {
78 | if (request instanceof UploadRequestBuilder uploadRequestBuilder) {
79 | return queueUpload(uploadRequestBuilder);
80 | } else if (request instanceof UrlRequestBuilder urlRequestBuilder) {
81 | return queueUrl(urlRequestBuilder);
82 | } else if (request instanceof UserRequestBuilder userRequestBuilder) {
83 | return queueUser(userRequestBuilder);
84 | }
85 | throw new MineskinException("Unknown request builder type: " + request.getClass());
86 | }
87 |
88 | CompletableFuture queueUpload(UploadRequestBuilder builder) {
89 | LOGGER.log(Level.FINER, "Adding upload request to internal queue: {0}", builder);
90 | return generateQueue.submit(() -> {
91 | try {
92 | Map data = builder.options().toMap();
93 | UploadSource source = builder.getUploadSource();
94 | checkNotNull(source);
95 | try (InputStream inputStream = source.getInputStream()) {
96 | LOGGER.log(Level.FINER, "Submitting to MineSkin queue: {0}", builder);
97 | QueueResponseImpl res = requestHandler.postFormDataFile("/v2/queue", "file", "mineskinjava", inputStream, data, JobInfo.class, QueueResponseImpl::new);
98 | handleGenerateResponse(res);
99 | return res;
100 | }
101 | } catch (IOException e) {
102 | throw new MineskinException(e);
103 | } catch (MineSkinRequestException e) {
104 | handleGenerateResponse(e.getResponse());
105 | throw e;
106 | }
107 | }, executors.generateExecutor());
108 | }
109 |
110 | CompletableFuture queueUrl(UrlRequestBuilder builder) {
111 | LOGGER.log(Level.FINER, "Adding url request to internal queue: {0}", builder);
112 | return generateQueue.submit(() -> {
113 | try {
114 | JsonObject body = builder.options().toJson();
115 | URL url = builder.getUrl();
116 | checkNotNull(url);
117 | body.addProperty("url", url.toString());
118 | LOGGER.log(Level.FINER, "Submitting to MineSkin queue: {0}", builder);
119 | QueueResponseImpl res = requestHandler.postJson("/v2/queue", body, JobInfo.class, QueueResponseImpl::new);
120 | handleGenerateResponse(res);
121 | return res;
122 | } catch (IOException e) {
123 | throw new MineskinException(e);
124 | } catch (MineSkinRequestException e) {
125 | handleGenerateResponse(e.getResponse());
126 | throw e;
127 | }
128 | }, executors.generateExecutor());
129 | }
130 |
131 | CompletableFuture queueUser(UserRequestBuilder builder) {
132 | LOGGER.log(Level.FINER, "Adding user request to internal queue: {0}", builder);
133 | return generateQueue.submit(() -> {
134 | try {
135 | JsonObject body = builder.options().toJson();
136 | UUID uuid = builder.getUuid();
137 | checkNotNull(uuid);
138 | body.addProperty("user", uuid.toString());
139 | LOGGER.log(Level.FINER, "Submitting to MineSkin queue: {0}", builder);
140 | QueueResponseImpl res = requestHandler.postJson("/v2/queue", body, JobInfo.class, QueueResponseImpl::new);
141 | handleGenerateResponse(res);
142 | return res;
143 | } catch (IOException e) {
144 | throw new MineskinException(e);
145 | } catch (MineSkinRequestException e) {
146 | handleGenerateResponse(e.getResponse());
147 | throw e;
148 | }
149 | }, executors.generateExecutor());
150 | }
151 |
152 | private void handleGenerateResponse(MineSkinResponse> response0) {
153 | if (!(response0 instanceof QueueResponse response)) return;
154 | RateLimitInfo rateLimit = response.getRateLimit();
155 | if (rateLimit == null) return;
156 | long nextRelative = rateLimit.next().relative();
157 | if (nextRelative > 0) {
158 | generateQueue.setNextRequest(Math.max(generateQueue.getNextRequest(), System.currentTimeMillis() + nextRelative));
159 | }
160 | }
161 |
162 | @Override
163 | public CompletableFuture get(JobInfo jobInfo) {
164 | checkNotNull(jobInfo);
165 | return get(jobInfo.id());
166 | }
167 |
168 | @Override
169 | public CompletableFuture get(String id) {
170 | checkNotNull(id);
171 | return CompletableFuture.supplyAsync(() -> {
172 | try {
173 | return requestHandler.getJson("/v2/queue/" + id, JobInfo.class, JobResponseImpl::new);
174 | } catch (IOException e) {
175 | throw new MineskinException(e);
176 | }
177 | }, executors.getExecutor());
178 | }
179 |
180 | @Override
181 | public CompletableFuture waitForCompletion(JobInfo jobInfo) {
182 | checkNotNull(jobInfo);
183 | if (jobInfo.id() == null) {
184 | return CompletableFuture.completedFuture(new NullJobReference(jobInfo));
185 | }
186 | IJobCheckOptions options = executors.jobCheckOptions();
187 | return new JobChecker(MineSkinClientImpl.this, jobInfo, options).check();
188 | }
189 |
190 |
191 | }
192 |
193 | class GenerateClientImpl implements GenerateClient {
194 |
195 | @Override
196 | public CompletableFuture submitAndWait(GenerateRequest request) {
197 | if (request instanceof UploadRequestBuilder uploadRequestBuilder) {
198 | return generateUpload(uploadRequestBuilder);
199 | } else if (request instanceof UrlRequestBuilder urlRequestBuilder) {
200 | return generateUrl(urlRequestBuilder);
201 | } else if (request instanceof UserRequestBuilder userRequestBuilder) {
202 | return generateUser(userRequestBuilder);
203 | }
204 | throw new MineskinException("Unknown request builder type: " + request.getClass());
205 | }
206 |
207 | CompletableFuture generateUpload(UploadRequestBuilder builder) {
208 | LOGGER.log(Level.FINER, "Adding upload request to internal generate queue: {0}", builder);
209 | return generateQueue.submit(() -> {
210 | try {
211 | Map data = builder.options().toMap();
212 | UploadSource source = builder.getUploadSource();
213 | checkNotNull(source);
214 | try (InputStream inputStream = source.getInputStream()) {
215 | LOGGER.log(Level.FINER, "Submitting to MineSkin generate: {0}", builder);
216 | GenerateResponseImpl res = requestHandler.postFormDataFile("/v2/generate", "file", "mineskinjava", inputStream, data, SkinInfo.class, GenerateResponseImpl::new);
217 | handleGenerateResponse(res);
218 | return res;
219 | }
220 | } catch (IOException e) {
221 | throw new MineskinException(e);
222 | } catch (MineSkinRequestException e) {
223 | handleGenerateResponse(e.getResponse());
224 | throw e;
225 | }
226 | }, executors.generateExecutor());
227 | }
228 |
229 | CompletableFuture generateUrl(UrlRequestBuilder builder) {
230 | LOGGER.log(Level.FINER, "Adding url request to internal generate queue: {0}", builder);
231 | return generateQueue.submit(() -> {
232 | try {
233 | JsonObject body = builder.options().toJson();
234 | URL url = builder.getUrl();
235 | checkNotNull(url);
236 | body.addProperty("url", url.toString());
237 | LOGGER.log(Level.FINER, "Submitting to MineSkin generate: {0}", builder);
238 | GenerateResponseImpl res = requestHandler.postJson("/v2/generate", body, SkinInfo.class, GenerateResponseImpl::new);
239 | handleGenerateResponse(res);
240 | return res;
241 | } catch (IOException e) {
242 | throw new MineskinException(e);
243 | } catch (MineSkinRequestException e) {
244 | handleGenerateResponse(e.getResponse());
245 | throw e;
246 | }
247 | }, executors.generateExecutor());
248 | }
249 |
250 | CompletableFuture generateUser(UserRequestBuilder builder) {
251 | LOGGER.log(Level.FINER, "Adding user request to internal generate queue: {0}", builder);
252 | return generateQueue.submit(() -> {
253 | try {
254 | JsonObject body = builder.options().toJson();
255 | UUID uuid = builder.getUuid();
256 | checkNotNull(uuid);
257 | body.addProperty("user", uuid.toString());
258 | LOGGER.log(Level.FINER, "Submitting to MineSkin generate: {0}", builder);
259 | GenerateResponseImpl res = requestHandler.postJson("/v2/generate", body, SkinInfo.class, GenerateResponseImpl::new);
260 | handleGenerateResponse(res);
261 | return res;
262 | } catch (IOException e) {
263 | throw new MineskinException(e);
264 | } catch (MineSkinRequestException e) {
265 | handleGenerateResponse(e.getResponse());
266 | throw e;
267 | }
268 | }, executors.generateExecutor());
269 | }
270 |
271 | private void handleGenerateResponse(MineSkinResponse> response0) {
272 | LOGGER.log(Level.FINER, "Handling generate response: {0}", response0);
273 | if (!(response0 instanceof GenerateResponse response)) return;
274 | RateLimitInfo rateLimit = response.getRateLimit();
275 | if (rateLimit == null) return;
276 | long nextRelative = rateLimit.next().relative();
277 | if (nextRelative > 0) {
278 | generateQueue.setNextRequest(Math.max(generateQueue.getNextRequest(), System.currentTimeMillis() + nextRelative));
279 | }
280 | }
281 |
282 | }
283 |
284 | class SkinsClientImpl implements SkinsClient {
285 |
286 | /**
287 | * Get an existing skin by UUID (Note: not the player's UUID)
288 | */
289 | @Override
290 | public CompletableFuture get(UUID uuid) {
291 | checkNotNull(uuid);
292 | return get(uuid.toString());
293 | }
294 |
295 | /**
296 | * Get an existing skin by UUID (Note: not the player's UUID)
297 | */
298 | @Override
299 | public CompletableFuture get(String uuid) {
300 | checkNotNull(uuid);
301 | return getQueue.submit(() -> {
302 | try {
303 | return requestHandler.getJson("/v2/skins/" + uuid, SkinInfo.class, SkinResponseImpl::new);
304 | } catch (IOException e) {
305 | throw new MineskinException(e);
306 | }
307 | }, executors.getExecutor());
308 | }
309 |
310 | }
311 |
312 | class MiscClientImpl implements MiscClient {
313 | @Override
314 | public CompletableFuture getUser() {
315 | return getQueue.submit(() -> {
316 | try {
317 | return requestHandler.getJson("/v2/me", UserInfo.class, UserResponseImpl::new);
318 | } catch (IOException e) {
319 | throw new MineskinException(e);
320 | }
321 | }, executors.getExecutor());
322 | }
323 | }
324 |
325 | }
326 |
--------------------------------------------------------------------------------
/tests/src/test/java/test/GenerateTest.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import org.junit.Before;
4 | import org.junit.Ignore;
5 | import org.junit.Test;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.Arguments;
8 | import org.junit.jupiter.params.provider.MethodSource;
9 | import org.mineskin.*;
10 | import org.mineskin.data.*;
11 | import org.mineskin.exception.MineSkinRequestException;
12 | import org.mineskin.request.GenerateRequest;
13 | import org.mineskin.response.GenerateResponse;
14 | import org.mineskin.response.JobResponse;
15 | import org.mineskin.response.QueueResponse;
16 |
17 | import javax.imageio.ImageIO;
18 | import java.awt.image.BufferedImage;
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.text.SimpleDateFormat;
22 | import java.util.Date;
23 | import java.util.HashMap;
24 | import java.util.Iterator;
25 | import java.util.Map;
26 | import java.util.concurrent.CompletionException;
27 | import java.util.concurrent.Executor;
28 | import java.util.concurrent.Executors;
29 | import java.util.concurrent.ThreadLocalRandom;
30 | import java.util.logging.ConsoleHandler;
31 | import java.util.logging.Level;
32 | import java.util.stream.Stream;
33 |
34 | import static org.junit.jupiter.api.Assertions.*;
35 |
36 | public class GenerateTest {
37 |
38 | static {
39 | // set logger to log milliseconds
40 | System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s %2$s: %5$s%6$s%n");
41 |
42 | MineSkinClientImpl.LOGGER.setLevel(Level.ALL);
43 | ConsoleHandler handler = new ConsoleHandler();
44 | handler.setLevel(Level.ALL);
45 | MineSkinClientImpl.LOGGER.addHandler(handler);
46 | }
47 |
48 | private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
49 |
50 | private static final MineSkinClient APACHE = MineSkinClient.builder()
51 | .requestHandler(ApacheRequestHandler::new)
52 | .userAgent("MineSkinClient-Apache/Tests")
53 | .apiKey(System.getenv("MINESKIN_API_KEY"))
54 | .generateExecutor(EXECUTOR)
55 | .build();
56 | private static final MineSkinClient JSOUP = MineSkinClient.builder()
57 | .requestHandler(JsoupRequestHandler::new)
58 | .userAgent("MineSkinClient-Jsoup/Tests")
59 | .apiKey(System.getenv("MINESKIN_API_KEY"))
60 | .generateExecutor(EXECUTOR)
61 | .build();
62 | private static final MineSkinClient JAVA11 = MineSkinClient.builder()
63 | .requestHandler(Java11RequestHandler::new)
64 | .userAgent("MineSkinClient-Java11/Tests")
65 | .apiKey(System.getenv("MINESKIN_API_KEY"))
66 | .generateExecutor(EXECUTOR)
67 | .build();
68 |
69 | @Before
70 | public void before() throws InterruptedException {
71 | Thread.sleep(1000);
72 | }
73 |
74 | private static Stream clients() {
75 | return Stream.of(
76 | Arguments.of(APACHE),
77 | Arguments.of(JSOUP),
78 | Arguments.of(JAVA11)
79 | );
80 | }
81 |
82 | // @ParameterizedTest
83 | // @MethodSource("clients")
84 | // public void urlTest(MineSkinClient client) throws InterruptedException {
85 | // Thread.sleep(1000);
86 | //
87 | // final String name = "JavaClient-Url";
88 | // try {
89 | // GenerateResponse res = client.generateUrl("https://i.imgur.com/jkhZKDX.png", GenerateOptions.create().name(name)).join();
90 | // log(res);
91 | // Skin skin = res.getSkin();
92 | // validateSkin(skin, name);
93 | // } catch (CompletionException e) {
94 | // if (e.getCause() instanceof MineSkinRequestException req) {
95 | // log(req.getResponse());
96 | // }
97 | // throw e;
98 | // }
99 | // Thread.sleep(1000);
100 | // }
101 |
102 | @ParameterizedTest
103 | @MethodSource("clients")
104 | public void singleQueueUploadTest(MineSkinClient client) throws InterruptedException, IOException {
105 | Thread.sleep(1000);
106 |
107 | File file = File.createTempFile("mineskin-temp-upload-image", ".png");
108 | ImageIO.write(ImageUtil.randomImage(64, ThreadLocalRandom.current().nextBoolean() ? 64 : 32), "png", file);
109 | log("#queueTest");
110 | long start = System.currentTimeMillis();
111 | try {
112 | String name = "mskjva-upl-" + ThreadLocalRandom.current().nextInt(1000);
113 | GenerateRequest request = GenerateRequest.upload(file)
114 | .visibility(Visibility.UNLISTED)
115 | .name(name);
116 | log("Submitting to queue: " + request);
117 | QueueResponse res = client.queue().submit(request).join();
118 | log("Queue submit took " + (System.currentTimeMillis() - start) + "ms");
119 | log(res);
120 | JobReference jobResponse = res.getBody().waitForCompletion(client).join();
121 | log("Job took " + (System.currentTimeMillis() - start) + "ms");
122 | log(jobResponse);
123 | SkinInfo skinInfo = jobResponse.getOrLoadSkin(client).join();
124 | validateSkin(skinInfo, name);
125 | } catch (CompletionException e) {
126 | if (e.getCause() instanceof MineSkinRequestException req) {
127 | log(req.getResponse());
128 | }
129 | throw e;
130 | }
131 | Thread.sleep(1000);
132 | }
133 |
134 | @ParameterizedTest
135 | @MethodSource("clients")
136 | public void singleQueueUrlTest(MineSkinClient client) throws InterruptedException, IOException {
137 | Thread.sleep(1000);
138 |
139 | long start = System.currentTimeMillis();
140 | try {
141 | String name = "mskjva-url-" + ThreadLocalRandom.current().nextInt(1000);
142 | GenerateRequest request = GenerateRequest.url("https://api.mineskin.org/random-image?t=" + System.currentTimeMillis())
143 | .visibility(Visibility.UNLISTED)
144 | .name(name);
145 | QueueResponse res = client.queue().submit(request).join();
146 | log("Queue submit took " + (System.currentTimeMillis() - start) + "ms");
147 | log(res);
148 | JobReference jobResponse = res.getBody().waitForCompletion(client).join();
149 | log("Job took " + (System.currentTimeMillis() - start) + "ms");
150 | log(jobResponse);
151 | SkinInfo skinInfo = jobResponse.getOrLoadSkin(client).join();
152 | validateSkin(skinInfo, name);
153 | } catch (CompletionException e) {
154 | if (e.getCause() instanceof MineSkinRequestException req) {
155 | log(req.getResponse());
156 | }
157 | throw e;
158 | }
159 | Thread.sleep(1000);
160 | }
161 |
162 | @Ignore
163 | @Test
164 | public void multiQueueRenderedUploadTest() throws InterruptedException, IOException {
165 | MineSkinClient client = JAVA11;
166 | int count = 5;
167 | Thread.sleep(1000);
168 |
169 | long start = System.currentTimeMillis();
170 | Map jobs = new HashMap<>();
171 | for (int i = 0; i < count; i++) {
172 | long jobStart = System.currentTimeMillis();
173 | try {
174 | Thread.sleep(100);
175 | String name = "mskjva-upl-" + i + "-" + ThreadLocalRandom.current().nextInt(1000);
176 | BufferedImage image = ImageUtil.randomImage(64, ThreadLocalRandom.current().nextBoolean() ? 64 : 32);
177 | GenerateRequest request = GenerateRequest.upload(image)
178 | .visibility(Visibility.UNLISTED)
179 | .name(name);
180 | QueueResponse res = client.queue().submit(request).join();
181 | log("Queue submit took " + (System.currentTimeMillis() - jobStart) + "ms");
182 | log(res);
183 | jobs.put(name, res.getBody());
184 | } catch (CompletionException e) {
185 | if (e.getCause() instanceof MineSkinRequestException req) {
186 | log(req.getResponse());
187 | }
188 | throw e;
189 | }
190 | }
191 |
192 | Map completedJobs = new HashMap<>();
193 | int jobsPending = 1;
194 | while (jobsPending > 0) {
195 | jobsPending = 0;
196 | Iterator> iterator = jobs.entrySet().iterator();
197 | for (; iterator.hasNext(); ) {
198 | Map.Entry entry = iterator.next();
199 | JobInfo jobInfo = entry.getValue();
200 | JobResponse jobResponse = client.queue().get(jobInfo).join();
201 | if (jobResponse.getJob().status().isPending()) {
202 | jobsPending++;
203 | } else {
204 | completedJobs.put(entry.getKey(), jobInfo);
205 | iterator.remove();
206 | }
207 | }
208 | log("Jobs pending: " + jobsPending);
209 | Thread.sleep(1000);
210 | }
211 |
212 | for (Map.Entry entry : completedJobs.entrySet()) {
213 | String name = entry.getKey();
214 | JobInfo jobInfo = entry.getValue();
215 | JobResponse jobResponse = client.queue().get(jobInfo).join();
216 | log("Job took " + (System.currentTimeMillis() - start) + "ms");
217 | log(jobResponse);
218 | assertTrue(jobResponse.getJob().status().isDone());
219 | assertTrue(jobResponse.getSkin().isPresent());
220 | SkinInfo skinInfo = jobResponse.getOrLoadSkin(client).join();
221 | validateSkin(skinInfo, name);
222 | }
223 |
224 |
225 | Thread.sleep(1000);
226 | }
227 |
228 | @ParameterizedTest
229 | @MethodSource("clients")
230 | public void duplicateQueueUrlTest(MineSkinClient client) throws InterruptedException, IOException {
231 | Thread.sleep(1000);
232 |
233 | long start = System.currentTimeMillis();
234 | try {
235 | String name = "mskjva-url";
236 | GenerateRequest request = GenerateRequest.url("https://i.imgur.com/ZC5PRM4.png")
237 | .visibility(Visibility.UNLISTED)
238 | .name(name);
239 | QueueResponse res = client.queue().submit(request).join();
240 | log("Queue submit took " + (System.currentTimeMillis() - start) + "ms");
241 | log(res);
242 | JobReference jobResponse = res.getBody().waitForCompletion(client).join();
243 | log("Job took " + (System.currentTimeMillis() - start) + "ms");
244 | log(jobResponse);
245 | SkinInfo skinInfo = jobResponse.getOrLoadSkin(client).join();
246 | validateSkin(skinInfo, name);
247 | } catch (CompletionException e) {
248 | if (e.getCause() instanceof MineSkinRequestException req) {
249 | log(req.getResponse());
250 | }
251 | throw e;
252 | }
253 | Thread.sleep(1000);
254 | }
255 |
256 | @ParameterizedTest
257 | @MethodSource("clients")
258 | public void singleGenerateUploadTest(MineSkinClient client) throws InterruptedException, IOException {
259 | Thread.sleep(1000);
260 |
261 | File file = File.createTempFile("mineskin-temp-upload-image", ".png");
262 | ImageIO.write(ImageUtil.randomImage(64, ThreadLocalRandom.current().nextBoolean() ? 64 : 32), "png", file);
263 | log("#queueTest");
264 | long start = System.currentTimeMillis();
265 | try {
266 | String name = "mskjva-upl-" + ThreadLocalRandom.current().nextInt(1000);
267 | GenerateRequest request = GenerateRequest.upload(file)
268 | .visibility(Visibility.UNLISTED)
269 | .name(name);
270 | GenerateResponse res = client.generate().submitAndWait(request).join();
271 | log("Generate took " + (System.currentTimeMillis() - start) + "ms");
272 | log(res);
273 | SkinInfo skinInfo = res.getSkin();
274 | validateSkin(skinInfo, name);
275 | } catch (CompletionException e) {
276 | if (e.getCause() instanceof MineSkinRequestException req) {
277 | log(req.getResponse());
278 | }
279 | throw e;
280 | }
281 | Thread.sleep(1000);
282 | }
283 |
284 | /*
285 | @ParameterizedTest
286 | @MethodSource("clients")
287 | public void uploadTest(MineSkinClient client) throws InterruptedException, IOException {
288 | Thread.sleep(1000);
289 |
290 | final String name = "JavaClient-Upload";
291 | File file = File.createTempFile("mineskin-temp-upload-image", ".png");
292 | ImageIO.write(ImageUtil.randomImage(64, ThreadLocalRandom.current().nextBoolean() ? 64 : 32), "png", file);
293 | log("#uploadTest");
294 | long start = System.currentTimeMillis();
295 | try {
296 | GenerateResponse res = client.generateUpload(file, GenerateOptions.create().visibility(Visibility.UNLISTED).name(name)).join();
297 | log("Upload took " + (System.currentTimeMillis() - start) + "ms");
298 | log(res);
299 | Skin skin = res.getSkin();
300 | validateSkin(skin, name);
301 | } catch (CompletionException e) {
302 | if (e.getCause() instanceof MineSkinRequestException req) {
303 | log(req.getResponse());
304 | }
305 | throw e;
306 | }
307 | Thread.sleep(1000);
308 | }
309 |
310 | */
311 |
312 | /*
313 | @ParameterizedTest
314 | @MethodSource("clients")
315 | public void uploadRenderedImageTest(MineSkinClient client) throws InterruptedException, IOException {
316 | Thread.sleep(1000);
317 |
318 | final String name = "JavaClient-Upload";
319 | log("#uploadRenderedImageTest");
320 | long start = System.currentTimeMillis();
321 | try {
322 | GenerateResponse res = client.generateUpload(ImageUtil.randomImage(64, ThreadLocalRandom.current().nextBoolean() ? 64 : 32), GenerateOptions.create().visibility(Visibility.UNLISTED).name(name)).join();
323 | log("Upload took " + (System.currentTimeMillis() - start) + "ms");
324 | log(res);
325 | Skin skin = res.getSkin();
326 | validateSkin(skin, name);
327 | } catch (CompletionException e) {
328 | if (e.getCause() instanceof MineSkinRequestException req) {
329 | log(req.getResponse());
330 | }
331 | throw e;
332 | }
333 | Thread.sleep(1000);
334 | }
335 | //
336 | // @Test()
337 | // public void multiUploadTest() throws InterruptedException, IOException {
338 | // for (int i = 0; i < 50; i++) {
339 | // try {
340 | // uploadTest();
341 | // uploadRenderedImageTest();
342 | // } catch (Exception e) {
343 | // e.printStackTrace();
344 | // }
345 | // }
346 | // }
347 | */
348 | void validateSkin(Skin skin, String name) {
349 | assertNotNull(skin);
350 | assertNotNull(skin.texture());
351 | assertNotNull(skin.texture().data());
352 | assertNotNull(skin.texture().data().value());
353 | assertNotNull(skin.texture().data().signature());
354 |
355 | assertEquals(name, skin.name());
356 | }
357 |
358 | static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS");
359 |
360 | void log(Object message) {
361 | Date date = new Date();
362 | System.out.println(String.format("[%s] %s", DATE_FORMAT.format(date), message));
363 | }
364 |
365 | }
366 |
--------------------------------------------------------------------------------