package handlers.cron; import com.github.masahitojp.botan.Robot; import com.github.masahitojp.botan.handler.BotanMessageHandlers; import com.github.masahitojp.botan.message.BotanMessageSimple; import com.google.common.base.Strings; import com.google.gson.Gson; import it.sauronsoftware.cron4j.InvalidPatternException; import it.sauronsoftware.cron4j.Scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @SuppressWarnings("unused") public class CronHandlers implements BotanMessageHandlers { public static String JOB_ADD_DESCRIPTION = "add job"; public static String JOB_LIST_DESCRIPTION = "show job list"; public static String JOB_RM_DESCRIPTION = "remove job from list"; private static Logger logger = LoggerFactory.getLogger(CronHandlers.class); private static String NAME_SPACE = "cronjob_"; private final Object lock = new Object(); private final ConcurrentHashMap<Integer, String> cronIds = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, CronJob> runningJobs = new ConcurrentHashMap<>(); private Scheduler scheduler; @Override public final void initialize(final Robot robot) { scheduler = new Scheduler(); // start cron4j scheduler. scheduler.start(); remember(robot); } private int genereteId() { final Random random = new Random(); int id; do { int JOB_ID_MAX = 10000; id = random.nextInt(JOB_ID_MAX); } while (cronIds.containsKey(id)); return id; } private void remember(final Robot robot) { final Gson gson = new Gson(); robot.getBrain().getData().forEach((k, v) -> { if (k.startsWith(NAME_SPACE)) { final String json = robot.getBrain().getData().getOrDefault(k, ""); final CronJob job = gson.fromJson(json, CronJob.class); try { final String id = scheduler.schedule(job.schedule, () -> { robot.send(new BotanMessageSimple(job.message, job.to, job.to, job.to, -1)); }); final int jobId = Integer.parseInt(k.substring(NAME_SPACE.length())); cronIds.put(jobId, id); runningJobs.put(id, job); } catch (final InvalidPatternException | NumberFormatException e) { logger.warn("job register failed: {}", e); } } }); } @Override public final void register(final Robot robot) { robot.respond( "job\\s+(?:new|add)\\s+\"(?<schedule>.+)\"\\s+(?<message>.+)\\z", JOB_ADD_DESCRIPTION, message -> { try { final Gson gson = new Gson(); final String id = scheduler.schedule(message.getMatcher().group("schedule"), () -> { message.reply(message.getMatcher().group("message")); }); final CronJob job = new CronJob(message.getMatcher().group("schedule"), message.getTo(), message.getMatcher().group("message")); runningJobs.put(id, job); final int jobId; synchronized (lock) { jobId = genereteId(); cronIds.put(jobId, id); } robot.getBrain().getData().put(NAME_SPACE + jobId, gson.toJson(job)); message.reply(String.valueOf(jobId)); } catch (final InvalidPatternException e) { message.reply("job register failed:" + e.getMessage()); } } ); robot.respond( "job\\s+(?:list|ls)\\z", JOB_LIST_DESCRIPTION, botanMessage -> { if (cronIds.isEmpty()) { botanMessage.reply("no jobs"); } else { botanMessage.reply( new TreeMap<> (cronIds).entrySet().stream().map(entry -> { final CronJob job = runningJobs.get(entry.getValue()); return String.format("%d: \"%s\" %s", entry.getKey(), job.schedule, job.message); }).collect(Collectors.joining("\n"))) ; } } ); robot.respond( "job\\s+(?:rm|remove|del|delete)\\s+(?<id>\\d+)\\z", JOB_RM_DESCRIPTION, message -> { try { final String userInputId = message.getMatcher().group("id"); final int jobId = Integer.parseInt(userInputId); final String id = cronIds.get(jobId); if (Strings.isNullOrEmpty(id)) { message.reply(String.format("job rm failed: id %d not found", jobId)); } else { scheduler.deschedule(id); cronIds.remove(jobId); runningJobs.remove(id); robot.getBrain().getData().remove(NAME_SPACE + userInputId); message.reply("job rm successful"); } } catch (final NumberFormatException e) { message.reply("job rm failed"); } } ); } @Override public final void beforeShutdown() { scheduler.stop(); } }