0.1.1 | Scheduler shenanigans

This commit is contained in:
Patrick
2026-06-05 14:08:07 +02:00
parent f9fa1af963
commit dcd8c8597d
9 changed files with 302 additions and 103 deletions
+69
View File
@@ -68,3 +68,72 @@ public class main {
}
}
```
### Usage Scheduler (without retries)
```java
import java.time.Duration;
public class main {
public static void main(String[] args) {
LicenseScheduler scheduler = LicenseScheduler.startScheduler(
Duration.ofMinutes(5),
apiUrl,
plugin,
licenseKey,
serverId,
result -> {
if (result instanceof LicenseSuccess success) {
if (!success.valid()) {
// Handle invalid license
}
} else if (result instanceof LicenseError error) {
// Log errors if you want
}
}
);
// Later: Stop the scheduler
scheduler.stop();
}
}
```
### Usage Scheduler (with retries)
```java
import java.time.Duration;
public class main {
public static void main(String[] args) {
LicenseScheduler scheduler = LicenseScheduler.startSchedulerWithRetries(
Duration.ofMinutes(5),
apiUrl,
plugin,
licenseKey,
serverId,
3, //max retries,
Duration.ofSeconds(10), //delay between retries
result -> {
if(result instanceof LicenseSuccess success) {
if (!success.valid()) {
// Handle invalid license
}
} else if (result instanceof LicenseError error) {
// Log errors if you want
}
},
result -> {
// Called only after all retries failed
// Put your "offline" handling here
// Disable plugin or something like that
}
);
// Later: Stop the scheduler
scheduler.stop();
}
}
```
-49
View File
@@ -1,49 +0,0 @@
plugins {
id 'java'
id 'maven-publish'
}
group = 'de.winniepat'
version = '0.1.0'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
withSourcesJar()
withJavadocJar()
}
repositories {
mavenCentral()
maven {url = "https://repo.papermc.io/repository/maven-public/" }
}
dependencies {
implementation 'com.google.code.gson:gson:2.13.1'
}
tasks.named('test') {
useJUnitPlatform()
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
url = uri("https://maven.winniepat.de/repository/maven-releases/")
credentials {
username = project.property("publish.username")
password = project.property("publish.password")
// the credentials from before are not valid anymore (I am not that dumb)
}
}
}
}
+36 -27
View File
@@ -1,41 +1,50 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java library project to get you started.
* For more details on building Java & JVM projects, please refer to https://docs.gradle.org/9.5.1/userguide/building_java_projects.html in the Gradle documentation.
*/
plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
id 'java'
id 'maven-publish'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
group = 'de.winniepat'
version = '0.1.1'
dependencies {
// Use JUnit Jupiter for testing.
testImplementation libs.junit.jupiter
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// This dependency is exported to consumers, that is to say found on their compile classpath.
api libs.commons.math3
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation libs.guava
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
withSourcesJar()
withJavadocJar()
}
repositories {
mavenCentral()
maven {url = "https://repo.papermc.io/repository/maven-public/" }
}
dependencies {
implementation 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT'
implementation 'com.google.code.gson:gson:2.13.1'
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
url = uri("https://maven.winniepat.de/repository/maven-releases/")
credentials {
username = project.property("publish.username")
password = project.property("publish.password")
// the credentials from before are not valid anymore (I am not that dumb)
}
}
}
}
@@ -4,7 +4,7 @@
* Licensed under the MIT License
*/
package de.winniepat.licenselib;
package licenselib;
import com.google.gson.*;
@@ -1,4 +1,4 @@
package de.winniepat.licenselib;
package licenselib;
/**
* Represents an error that occurred during the license check process, such as network issues or invalid responses from the server.
@@ -0,0 +1,194 @@
package licenselib;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* Periodically checks a license and exposes the most recent result.
*/
public final class LicenseScheduler implements AutoCloseable {
private final ScheduledExecutorService executor;
private final ScheduledFuture<?> task;
private final AtomicReference<LicenseClient.LicenseResult> lastResult;
private LicenseScheduler(
ScheduledExecutorService executor,
ScheduledFuture<?> task,
AtomicReference<LicenseClient.LicenseResult> lastResult
) {
this.executor = executor;
this.task = task;
this.lastResult = lastResult;
}
/**
* Starts a scheduler that checks the license at the given interval.
* @param interval Interval between checks
* @param apiUrl Server API url
* @param plugin Plugin id matching the one on the backend api
* @param licenseKey License key issued by the api
* @param serverId Server id matching the one on the backend api
* @param onResult Optional callback invoked after every check
* @return Scheduler handle for stopping and reading the last result
*/
public static LicenseScheduler startScheduler(
Duration interval,
String apiUrl,
String plugin,
String licenseKey,
String serverId,
Consumer<LicenseClient.LicenseResult> onResult
) {
Objects.requireNonNull(interval, "interval");
if (interval.isZero() || interval.isNegative()) {
throw new IllegalArgumentException("interval must be positive");
}
AtomicReference<LicenseClient.LicenseResult> lastResult = new AtomicReference<>();
ThreadFactory threadFactory = runnable -> {
Thread thread = new Thread(runnable, "license-scheduler");
thread.setDaemon(true);
return thread;
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
ScheduledFuture<?> task = executor.scheduleAtFixedRate(() -> {
LicenseClient.LicenseResult result = LicenseClient.check(apiUrl, plugin, licenseKey, serverId);
lastResult.set(result);
if (onResult != null) {
onResult.accept(result);
}
}, 0, interval.toMillis(), TimeUnit.MILLISECONDS);
return new LicenseScheduler(executor, task, lastResult);
}
/**
* Starts a scheduler that retries on errors before invoking offline handling.
* @param interval Interval between checks
* @param apiUrl Server API url
* @param plugin Plugin id matching the one on the backend api
* @param licenseKey License key issued by the api
* @param serverId Server id matching the one on the backend api
* @param maxRetries Number of retries after a failed check
* @param retryDelay Delay between retries
* @param onResult Optional callback invoked after every attempt
* @param onOffline Invoked only after all retries fail
* @return Scheduler handle for stopping and reading the last result
*/
public static LicenseScheduler startSchedulerWithRetries(
Duration interval,
String apiUrl,
String plugin,
String licenseKey,
String serverId,
int maxRetries,
Duration retryDelay,
Consumer<LicenseClient.LicenseResult> onResult,
Consumer<LicenseClient.LicenseResult> onOffline
) {
Objects.requireNonNull(interval, "interval");
Objects.requireNonNull(retryDelay, "retryDelay");
if (interval.isZero() || interval.isNegative()) {
throw new IllegalArgumentException("interval must be positive");
}
if (retryDelay.isNegative()) {
throw new IllegalArgumentException("retryDelay must not be negative");
}
if (maxRetries < 0) {
throw new IllegalArgumentException("maxRetries must not be negative");
}
AtomicReference<LicenseClient.LicenseResult> lastResult = new AtomicReference<>();
ThreadFactory threadFactory = runnable -> {
Thread thread = new Thread(runnable, "license-scheduler");
thread.setDaemon(true);
return thread;
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
ScheduledFuture<?> task = executor.scheduleAtFixedRate(() -> {
LicenseClient.LicenseResult result = attemptWithRetries(
apiUrl,
plugin,
licenseKey,
serverId,
maxRetries,
retryDelay,
onResult,
onOffline
);
lastResult.set(result);
}, 0, interval.toMillis(), TimeUnit.MILLISECONDS);
return new LicenseScheduler(executor, task, lastResult);
}
private static LicenseClient.LicenseResult attemptWithRetries(
String apiUrl,
String plugin,
String licenseKey,
String serverId,
int maxRetries,
Duration retryDelay,
Consumer<LicenseClient.LicenseResult> onResult,
Consumer<LicenseClient.LicenseResult> onOffline
) {
int attempt = 0;
LicenseClient.LicenseResult result;
while (true) {
result = LicenseClient.check(apiUrl, plugin, licenseKey, serverId);
if (onResult != null) {
onResult.accept(result);
}
if (!(result instanceof LicenseError)) {
return result;
}
if (attempt >= maxRetries) {
if (onOffline != null) {
onOffline.accept(result);
}
return result;
}
attempt++;
if (!retryDelay.isZero()) {
try {
Thread.sleep(retryDelay.toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return result;
}
}
}
}
/**
* Returns the most recent license check result, or null if none has completed yet.
* @return The last license check result, or null if not available
*/
public LicenseClient.LicenseResult getLastResult() {
return lastResult.get();
}
/**
* Stops further checks and shuts down the scheduler.
*/
public void stop() {
task.cancel(false);
executor.shutdown();
}
@Override
public void close() {
stop();
}
}
@@ -1,4 +1,4 @@
package de.winniepat.licenselib;
package licenselib;
/**
* Represents a successful license check result, containing all relevant information about the license status, plugin, customer, and expiration details.
@@ -1,10 +0,0 @@
/*
* This source file was generated by the Gradle 'init' task
*/
package org.example;
public class Library {
public boolean someLibraryMethod() {
return true;
}
}
@@ -1,14 +0,0 @@
/*
* This source file was generated by the Gradle 'init' task
*/
package org.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LibraryTest {
@Test void someLibraryMethodReturnsTrue() {
Library classUnderTest = new Library();
assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'");
}
}