diff --git a/src/main/java/com/softawii/capivara/core/CalendarManager.java b/src/main/java/com/softawii/capivara/core/CalendarManager.java index 881ee4b..bd164c4 100644 --- a/src/main/java/com/softawii/capivara/core/CalendarManager.java +++ b/src/main/java/com/softawii/capivara/core/CalendarManager.java @@ -1,6 +1,7 @@ package com.softawii.capivara.core; import com.softawii.capivara.entity.Calendar; +import com.softawii.capivara.exceptions.DuplicatedKeyException; import com.softawii.capivara.services.CalendarService; import com.softawii.capivara.threads.CalendarSubscriptionThread; import net.dv8tion.jda.api.JDA; @@ -24,7 +25,7 @@ public CalendarManager(JDA jda, CalendarService service, CalendarSubscriptionThr this.subscriber = subscriber; } - public void createCalendar(String googleCalendarId, String name, GuildChannelUnion channel, Role role) { + public void createCalendar(String googleCalendarId, String name, GuildChannelUnion channel, Role role) throws DuplicatedKeyException { Long guildId = channel.getGuild().getIdLong(); Long channelId = channel.getIdLong(); Long roleId = role != null ? role.getIdLong() : null; diff --git a/src/main/java/com/softawii/capivara/entity/internal/CalendarSubscriber.java b/src/main/java/com/softawii/capivara/entity/internal/CalendarSubscriber.java new file mode 100644 index 0000000..c26bc30 --- /dev/null +++ b/src/main/java/com/softawii/capivara/entity/internal/CalendarSubscriber.java @@ -0,0 +1,165 @@ +package com.softawii.capivara.entity.internal; + +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.util.DateTime; +import com.google.api.services.calendar.model.Event; +import com.google.api.services.calendar.model.EventDateTime; +import com.google.api.services.calendar.model.Events; +import com.softawii.capivara.core.CalendarManager; +import com.softawii.capivara.entity.Calendar; +import com.softawii.capivara.utils.CalendarUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.text.ParseException; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +public class CalendarSubscriber { + private final Logger LOGGER = LogManager.getLogger(CalendarSubscriber.class); + private String googleCalendarId; + private List consumers; + private com.google.api.services.calendar.Calendar calendarService; + private HashMap events; + + private class EventWrapper extends TimerTask { + private Event event; + private final Timer timer; + + public EventWrapper(Event event) { + this.event = event; + this.timer = new Timer(); + setTimer(); + } + + private void setTimer() { + EventDateTime eventStart = this.event.getStart(); + EventDateTime eventEnd = this.event.getEnd(); + Instant now = Instant.now(); + DateTime dateStart; + DateTime dateEnd; + boolean isAllDayEvent = eventStart.getDate() != null && eventEnd.getDate() != null; + + if (isAllDayEvent) { + dateStart = eventStart.getDate(); + dateEnd = eventEnd.getDate(); + } else { + dateStart = eventStart.getDateTime(); + dateEnd = eventEnd.getDateTime(); + } + + boolean alreadyStarted = dateStart.getValue() <= now.toEpochMilli(); + boolean alreadyEnded = dateEnd.getValue() <= now.toEpochMilli(); + + if(!alreadyEnded && !alreadyStarted) { + Date scheduled = new Date(dateStart.getValue()); + LOGGER.info("Event scheduled: " + this.event.getSummary() + ", date: " + scheduled); + this.timer.schedule(this, scheduled); + } + } + + public void setEvent(Event event) { + if(this.event.getStart() != event.getStart()) { + this.timer.cancel(); + this.timer.purge(); + setTimer(); + } + + this.event = event; + } + + @Override + public void run() { + LOGGER.info("Event started: " + this.event.getSummary()); + } + } + + public CalendarSubscriber(String googleCalendarId, com.google.api.services.calendar.Calendar calendarService, Calendar.CalendarKey consumer) { + this.googleCalendarId = googleCalendarId; + this.calendarService = calendarService; + this.events = new HashMap<>(); + this.consumers = new ArrayList<>(); + this.consumers.add(consumer); + update(); + } + + public void subscribe(Calendar.CalendarKey consumer) { + if (!this.consumers.contains(consumer)) { + this.consumers.add(consumer); + } + } + + public void unsubscribe(Calendar.CalendarKey consumer) { + this.consumers.remove(consumer); + } + + private List getEvents() { + List items = new ArrayList<>(); + String pageToken = null; + do { + try { + Events events = calendarService + .events() + .list(googleCalendarId) + .setSingleEvents(true) + .setTimeMin(CalendarUtil.getMinDateTime(-1)) + .setTimeMax(CalendarUtil.getMaxDateTime(1)) + .setOrderBy("startTime") + .setPageToken(pageToken) + .execute(); + items.addAll(events.getItems()); + pageToken = events.getNextPageToken(); + } catch (GoogleJsonResponseException e) { + if ((e.getStatusCode() == 403 && !e.getStatusMessage().equals("Forbidden")) || e.getStatusCode() == 429) { + LOGGER.error(e.getDetails().getMessage() + " - rate limit - " + e.getDetails().getCode()); + return null; + } else { + LOGGER.error(e.getDetails().getMessage()); + return null; + } + } catch (IOException e) { + LOGGER.error(e.getMessage()); + return null; + } + } while (pageToken != null); + + return items; + } + + private void checkForUpdates(List events) { + // Removing events + Set localKeys = this.events.keySet(); + Set remoteKeys = events.stream().map(Event::getId).collect(Collectors.toSet()); + localKeys.removeAll(remoteKeys); + + // Removing events + for (String key : localKeys) { + EventWrapper eventWrapper = this.events.get(key); + LOGGER.info("Event removed: " + eventWrapper.event.getSummary()); + eventWrapper.cancel(); + eventWrapper.timer.purge(); + this.events.remove(key); + } + + // Updating events + for (Event event : events) { + String eventId = event.getId(); + EventWrapper eventWrapper = this.events.get(eventId); + if (eventWrapper == null) { + this.events.put(eventId, new EventWrapper(event)); + LOGGER.info("Event added: " + event.getSummary()); + } else { + eventWrapper.setEvent(event); + } + } + } + + public void update() { + List events = getEvents(); + if (events != null) { + checkForUpdates(events); + } + } +} diff --git a/src/main/java/com/softawii/capivara/exceptions/DuplicatedKeyException.java b/src/main/java/com/softawii/capivara/exceptions/DuplicatedKeyException.java new file mode 100644 index 0000000..ed11e59 --- /dev/null +++ b/src/main/java/com/softawii/capivara/exceptions/DuplicatedKeyException.java @@ -0,0 +1,23 @@ +package com.softawii.capivara.exceptions; + +public class DuplicatedKeyException extends Exception { + + public DuplicatedKeyException() { + } + + public DuplicatedKeyException(String message) { + super(message); + } + + public DuplicatedKeyException(String message, Throwable cause) { + super(message, cause); + } + + public DuplicatedKeyException(Throwable cause) { + super(cause); + } + + public DuplicatedKeyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/softawii/capivara/listeners/CalendarGroup.java b/src/main/java/com/softawii/capivara/listeners/CalendarGroup.java index a92af38..b0ebe08 100644 --- a/src/main/java/com/softawii/capivara/listeners/CalendarGroup.java +++ b/src/main/java/com/softawii/capivara/listeners/CalendarGroup.java @@ -8,6 +8,8 @@ import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/softawii/capivara/services/CalendarService.java b/src/main/java/com/softawii/capivara/services/CalendarService.java index 5d53a2c..5f18e61 100644 --- a/src/main/java/com/softawii/capivara/services/CalendarService.java +++ b/src/main/java/com/softawii/capivara/services/CalendarService.java @@ -1,6 +1,7 @@ package com.softawii.capivara.services; import com.softawii.capivara.entity.Calendar; +import com.softawii.capivara.exceptions.DuplicatedKeyException; import com.softawii.capivara.repository.CalendarRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -25,10 +26,10 @@ public Optional find(Calendar.CalendarKey calendarKey) { return repository.findById(calendarKey); } - public void create(Calendar calendar) { + public void create(Calendar calendar) throws DuplicatedKeyException { if (repository.existsById(calendar.getCalendarKey())) { // TODO: create new exception - throw new RuntimeException("Calendar already exists with this name and guild"); + throw new DuplicatedKeyException("Calendar already exists with this name and guild, use update instead"); } repository.save(calendar); } diff --git a/src/main/java/com/softawii/capivara/threads/CalendarSubscriptionThread.java b/src/main/java/com/softawii/capivara/threads/CalendarSubscriptionThread.java index f6ecd37..261ea6a 100644 --- a/src/main/java/com/softawii/capivara/threads/CalendarSubscriptionThread.java +++ b/src/main/java/com/softawii/capivara/threads/CalendarSubscriptionThread.java @@ -3,6 +3,7 @@ import com.google.api.services.calendar.model.Event; import com.google.api.services.calendar.model.Events; import com.softawii.capivara.entity.Calendar; +import com.softawii.capivara.entity.internal.CalendarSubscriber; import com.softawii.capivara.services.CalendarService; import net.dv8tion.jda.api.JDA; import org.apache.logging.log4j.LogManager; @@ -38,6 +39,7 @@ public class CalendarSubscriptionThread implements Runnable { private final JDA jda; private final com.google.api.services.calendar.Calendar googleCalendarService; private final CalendarService calendarService; +// private final HashMap public CalendarSubscriptionThread(JDA jda, CalendarService calendarService, com.google.api.services.calendar.Calendar googleCalendarService) { this.jda = jda; @@ -45,36 +47,26 @@ public CalendarSubscriptionThread(JDA jda, CalendarService calendarService, com. this.queue = new LinkedBlockingQueue<>(); this.scheduler = Executors.newScheduledThreadPool(1); this.calendarService = calendarService; + + startupSubscriber(); + + Thread thread = new Thread(this); + thread.start(); + LOGGER.info("CalendarSubscriptionThread started"); } - public void niceName() { + private void startupSubscriber() { PageRequest request = PageRequest.of(0, 100); Page calendarPage = this.calendarService.findAll(request); while (calendarPage.hasContent()) { - calendarPage.forEach(calendar -> { - Calendar.CalendarKey calendarKey = calendar.getCalendarKey(); - String name = calendarKey.getCalendarName(); - Long guildId = calendarKey.getGuildId(); - }); - + calendarPage.forEach(this::subscribe); request = request.next(); calendarPage = this.calendarService.findAll(request); } } - private void subscribe(Calendar calendar) throws IOException { - String pageToken = null; - do { - Events events = googleCalendarService.events() - .list(calendar.getGoogleCalendarId()) - .setPageToken(pageToken) - .execute(); - List items = events.getItems(); - for (Event event : items) { - LOGGER.info(event.getSummary()); - } - pageToken = events.getNextPageToken(); - } while (pageToken != null); + private void subscribe(Calendar calendar) { + new CalendarSubscriber(calendar.getGoogleCalendarId(), this.googleCalendarService, calendar.getCalendarKey()); } @Override