Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Commit 6ea1a81

Browse files
authored
Merge pull request #20 from Java-Discord/andrew/more_improvements
Improved Scheduling And Some Cleanup
2 parents 12445a6 + 1bd21ff commit 6ea1a81

File tree

6 files changed

+140
-31
lines changed

6 files changed

+140
-31
lines changed

build.gradle

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ dependencies {
2727
implementation 'com.h2database:h2:1.4.200'
2828
implementation 'com.zaxxer:HikariCP:5.0.0'
2929

30+
implementation 'org.quartz-scheduler:quartz:2.3.2'
31+
3032
// Lombok annotations
3133
compileOnly 'org.projectlombok:lombok:1.18.22'
3234
annotationProcessor 'org.projectlombok:lombok:1.18.22'
3335
testCompileOnly 'org.projectlombok:lombok:1.18.22'
3436
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
3537

3638
// Logging
37-
implementation 'ch.qos.logback:logback-classic:1.2.6'
39+
implementation 'ch.qos.logback:logback-classic:1.2.7'
3840

3941
// JUnit tests
4042
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'

src/main/java/net/javadiscord/javabot2/Bot.java

+11-30
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
import net.javadiscord.javabot2.command.SlashCommandListener;
1111
import net.javadiscord.javabot2.config.BotConfig;
1212
import net.javadiscord.javabot2.db.DbHelper;
13-
import net.javadiscord.javabot2.systems.moderation.ModerationService;
13+
import net.javadiscord.javabot2.tasks.ScheduledTasks;
1414
import org.javacord.api.DiscordApi;
1515
import org.javacord.api.DiscordApiBuilder;
1616
import org.javacord.api.entity.intent.Intent;
17+
import org.quartz.SchedulerException;
1718

1819
import java.nio.file.Path;
1920
import java.time.ZoneOffset;
2021
import java.util.TimeZone;
2122
import java.util.concurrent.Executors;
2223
import java.util.concurrent.ScheduledExecutorService;
23-
import java.util.concurrent.TimeUnit;
2424

2525
/**
2626
* The main program entry point.
@@ -32,22 +32,6 @@ public class Bot {
3232
*/
3333
public static HikariDataSource hikariDataSource;
3434

35-
/**
36-
* A thread-safe MongoDB client that can be used to interact with MongoDB.
37-
* @deprecated Use the relational data source for all future persistence
38-
* needs; it promotes more organized code that's less prone to failures.
39-
*/
40-
@Deprecated
41-
public static MongoClient mongoClient;
42-
43-
/**
44-
* The single Mongo database where all bot data is stored.
45-
* @deprecated Use the relational data source for all future persistence
46-
* needs; it promotes more organized code that's less prone to failures.
47-
*/
48-
@Deprecated
49-
public static MongoDatabase mongoDb;
50-
5135
/**
5236
* The bot's configuration.
5337
*/
@@ -81,7 +65,13 @@ public static void main(String[] args) {
8165
"commands/moderation.yaml"
8266
);
8367
api.addSlashCommandCreateListener(commandListener);
84-
initScheduledTasks(api);
68+
try {
69+
ScheduledTasks.init(api);
70+
log.info("Initialized scheduled tasks.");
71+
} catch (SchedulerException e) {
72+
log.error("Could not initialize all scheduled tasks.", e);
73+
api.disconnect().join();
74+
}
8575
}
8676

8777
/**
@@ -95,24 +85,15 @@ private static void initDataSources() {
9585
throw new IllegalStateException("Missing required Discord bot token! Please edit config/systems.json to add it, then run again.");
9686
}
9787
hikariDataSource = DbHelper.initDataSource(config);
98-
mongoDb = initMongoDatabase();
9988
}
10089

90+
@Deprecated
10191
private static MongoDatabase initMongoDatabase() {
102-
mongoClient = new MongoClient(new MongoClientURI(config.getSystems().getMongoDatabaseUrl()));
92+
var mongoClient = new MongoClient(new MongoClientURI(config.getSystems().getMongoDatabaseUrl()));
10393
var db = mongoClient.getDatabase("javabot");
10494
var warnCollection = db.getCollection("warn");
10595
warnCollection.createIndex(Indexes.ascending("userId"), new IndexOptions().unique(false));
10696
warnCollection.createIndex(Indexes.descending("createdAt"), new IndexOptions().unique(false));
10797
return db;
10898
}
109-
110-
private static void initScheduledTasks(DiscordApi api) {
111-
// Regularly check for and unmute users whose mutes have expired.
112-
asyncPool.scheduleAtFixedRate(() -> {
113-
for (var server : api.getServers()) {
114-
new ModerationService(api, config.get(server).getModeration()).unmuteExpired();
115-
}
116-
}, 1L, 1L, TimeUnit.MINUTES);
117-
}
11899
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package net.javadiscord.javabot2.tasks;
2+
3+
import net.javadiscord.javabot2.tasks.jobs.DiscordApiJob;
4+
import net.javadiscord.javabot2.tasks.jobs.UnmuteExpiredJob;
5+
import org.javacord.api.DiscordApi;
6+
import org.quartz.*;
7+
import org.quartz.impl.StdSchedulerFactory;
8+
9+
/**
10+
* This class is responsible for setting up all scheduled tasks that the bot
11+
* should run periodically, using the Quartz {@link Scheduler}. To add new tasks
12+
* to the schedule, add them to the {@link ScheduledTasks#scheduleAllTasks(Scheduler, DiscordApi)}
13+
* method.
14+
*/
15+
public class ScheduledTasks {
16+
// Hide the constructor.
17+
private ScheduledTasks() {}
18+
19+
/**
20+
* Initializes all scheduled jobs and starts the scheduler. Also adds a
21+
* shutdown hook that gracefully stops the scheduler when the program ends.
22+
* @param api The Discord API, which may be needed by some jobs.
23+
* @throws SchedulerException If an error occurs while starting the scheduler.
24+
*/
25+
public static void init(DiscordApi api) throws SchedulerException {
26+
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
27+
scheduleAllTasks(scheduler, api);
28+
scheduler.start();
29+
// Add a hook to shut down the scheduler cleanly when the program terminates.
30+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
31+
try {
32+
scheduler.shutdown();
33+
} catch (SchedulerException e) {
34+
e.printStackTrace();
35+
}
36+
}));
37+
}
38+
39+
/**
40+
* This method is where all tasks are scheduled. <strong>Add scheduled tasks
41+
* to the scheduler using this method!</strong>
42+
* @param scheduler The scheduler to use.
43+
* @param api The Discord API.
44+
* @throws SchedulerException If an error occurs while adding a task.
45+
*/
46+
private static void scheduleAllTasks(Scheduler scheduler, DiscordApi api) throws SchedulerException {
47+
scheduleApiJob(scheduler, api, UnmuteExpiredJob.class, SimpleScheduleBuilder.repeatMinutelyForever());
48+
}
49+
50+
/**
51+
* Convenience method for scheduling an API-dependent job using a single
52+
* trigger that follows a given schedule.
53+
* @param scheduler The scheduler to add the job to.
54+
* @param api The Discord API.
55+
* @param type The type of job to schedule.
56+
* @param scheduleBuilder A schedule builder that the trigger will use.
57+
* @throws SchedulerException If an error occurs while adding the job.
58+
* @see SimpleScheduleBuilder
59+
* @see CronScheduleBuilder
60+
* @see CalendarIntervalScheduleBuilder
61+
*/
62+
private static void scheduleApiJob(
63+
Scheduler scheduler,
64+
DiscordApi api,
65+
Class<? extends DiscordApiJob> type,
66+
ScheduleBuilder<?> scheduleBuilder
67+
) throws SchedulerException {
68+
scheduler.scheduleJob(
69+
DiscordApiJob.build(type, api),
70+
TriggerBuilder.newTrigger().withSchedule(scheduleBuilder).build()
71+
);
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package net.javadiscord.javabot2.tasks.jobs;
2+
3+
import org.javacord.api.DiscordApi;
4+
import org.quartz.*;
5+
6+
import java.util.Map;
7+
8+
/**
9+
* A type of job which requires a reference to a {@link DiscordApi} to be
10+
* available at execution time. Extend this class if your job needs the api.
11+
*/
12+
public abstract class DiscordApiJob implements Job {
13+
@Override
14+
public void execute(JobExecutionContext context) throws JobExecutionException {
15+
DiscordApi api = (DiscordApi) context.getJobDetail().getJobDataMap().get("discord-api");
16+
execute(context, api);
17+
}
18+
19+
protected abstract void execute(JobExecutionContext context, DiscordApi api) throws JobExecutionException;
20+
21+
/**
22+
* Builder method that produces a {@link JobDetail} for the given job type,
23+
* with job data initialized to include a reference to the given Discord API.
24+
* @param jobType The type of job to create a job detail for.
25+
* @param api The Discord API.
26+
* @return The created job detail.
27+
*/
28+
public static JobDetail build(Class<? extends DiscordApiJob> jobType, DiscordApi api) {
29+
return JobBuilder.newJob(jobType)
30+
.usingJobData(new JobDataMap(Map.of("discord-api", api)))
31+
.build();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package net.javadiscord.javabot2.tasks.jobs;
2+
3+
import net.javadiscord.javabot2.Bot;
4+
import net.javadiscord.javabot2.systems.moderation.ModerationService;
5+
import org.javacord.api.DiscordApi;
6+
import org.quartz.JobExecutionContext;
7+
8+
/**
9+
* Job which unmutes users whose mutes have expired.
10+
*/
11+
public class UnmuteExpiredJob extends DiscordApiJob {
12+
@Override
13+
protected void execute(JobExecutionContext context, DiscordApi api) {
14+
for (var server : api.getServers()) {
15+
new ModerationService(api, Bot.config.get(server).getModeration()).unmuteExpired();
16+
}
17+
}
18+
}

src/main/resources/quartz.properties

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.quartz.threadPool.threadCount = 4
2+
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

0 commit comments

Comments
 (0)