package org.fluxtream.connectors.google_calendar;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.connectors.annotations.Updater;
import org.fluxtream.core.connectors.updaters.AbstractUpdater;
import org.fluxtream.core.connectors.updaters.SettingsAwareUpdater;
import org.fluxtream.core.connectors.updaters.SharedConnectorSettingsAwareUpdater;
import org.fluxtream.core.connectors.updaters.UpdateFailedException;
import org.fluxtream.core.connectors.updaters.UpdateInfo;
import org.fluxtream.core.domain.ApiKey;
import org.fluxtream.core.domain.ChannelMapping;
import org.fluxtream.core.domain.Notification;
import org.fluxtream.core.domain.SharedConnector;
import org.fluxtream.core.services.ApiDataService;
import org.fluxtream.core.services.BuddiesService;
import org.fluxtream.core.services.JPADaoService;
import org.fluxtream.core.services.SettingsService;
import org.fluxtream.core.services.impl.BodyTrackHelper;
import org.fluxtream.core.utils.Utils;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.util.DateTime;
import com.google.api.services.calendar.Calendar;
import com.google.api.services.calendar.model.CalendarList;
import com.google.api.services.calendar.model.CalendarListEntry;
import com.google.api.services.calendar.model.Event;
import com.google.api.services.calendar.model.Events;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.joda.time.DateTimeConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static org.fluxtream.core.utils.Utils.hash;
@Component
@Updater(prettyName = "Calendar", value = 0, objectTypes={GoogleCalendarEventFacet.class},
settings=GoogleCalendarConnectorSettings.class, bodytrackResponder = GoogleCalendarBodytrackResponder.class,
defaultChannels = {"google_calendar.events"},
sharedConnectorFilter = GoogleCalendarSharedConnectorFilter.class)
public class GoogleCalendarUpdater extends AbstractUpdater implements SettingsAwareUpdater, SharedConnectorSettingsAwareUpdater {
private static final FlxLogger logger = FlxLogger.getLogger(GoogleCalendarUpdater.class);
private final String LAST_TIME_WE_CHECKED_FOR_UPDATED_EVENTS_ATT = "lastTimeWeCheckedForUpdatedEvents";
private final String REMOTE_CALLENDARS_KEY = "remoteCalendars";
@Autowired
JPADaoService jpaDaoService;
@Autowired
SettingsService settingsService;
@Autowired
BodyTrackHelper bodyTrackHelper;
@Autowired
BuddiesService buddiesService;
@Override
protected void updateConnectorDataHistory(UpdateInfo updateInfo) throws Exception, UpdateFailedException {
// if we're coming from an older install and this user has oauth 1 keys,
// suggest renewing the tokens in the manage connectors dialog
if (guestService.getApiKeyAttribute(updateInfo.apiKey, "googleConsumerKey")!=null||
guestService.getApiKeyAttribute(updateInfo.apiKey, "refreshToken")==null) {
sendOauth2UpgradeWarning(updateInfo);
} else {
// Store a conservative value for the last time we checked for updated events so that we
// don't miss updates that happen between the start of the check and when the check completes
String lastTimeCheckedForUpdatedEvents = String.valueOf(System.currentTimeMillis());
loadHistory(updateInfo, false);
guestService.setApiKeyAttribute(updateInfo.apiKey, LAST_TIME_WE_CHECKED_FOR_UPDATED_EVENTS_ATT, lastTimeCheckedForUpdatedEvents);
}
}
private void sendOauth2UpgradeWarning(final UpdateInfo updateInfo) throws UpdateFailedException {
notificationsService.addNamedNotification(updateInfo.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(),
"Heads Up. This server has recently been upgraded to a version that supports<br>" +
"oauth 2 with Google APIs. Please head to <a href=\"javascript:App.manageConnectors()\">Manage Connectors</a>,<br>" +
"scroll to the Google Calendar connector, and renew your tokens (look for the <i class=\"icon-resize-small icon-large\"></i> icon)");
// Report this connector as having failed permanently
throw new UpdateFailedException("requires token reauthorization", true, ApiKey.PermanentFailReason.NEEDS_REAUTH);
}
@Override
public void updateConnectorData(UpdateInfo updateInfo) throws Exception, UpdateFailedException {
// if we're coming from an older install and this user has oauth 1 keys,
// suggest renewing the tokens in the manage connectors dialog
if (guestService.getApiKeyAttribute(updateInfo.apiKey, "googleConsumerKey")!=null||
guestService.getApiKeyAttribute(updateInfo.apiKey, "refreshToken")==null) {
sendOauth2UpgradeWarning(updateInfo);
} else {
loadHistory(updateInfo, true);
}
}
@Override
public void connectorSettingsChanged(final long apiKeyId, final Object settings) {
final GoogleCalendarConnectorSettings connectorSettings = (GoogleCalendarConnectorSettings)settings;
final ApiKey apiKey = guestService.getApiKey(apiKeyId);
initChannelMapping(apiKey, connectorSettings.calendars);
}
private void initChannelMapping(ApiKey apiKey, final List<CalendarConfig> calendarConfigs) {
bodyTrackHelper.deleteChannelMappings(apiKey);
ChannelMapping mapping = new ChannelMapping();
mapping.deviceName = "google_calendar";
mapping.channelName = "events";
mapping.timeType = ChannelMapping.TimeType.gmt;
mapping.channelType = ChannelMapping.ChannelType.timespan;
mapping.guestId = apiKey.getGuestId();
mapping.apiKeyId = apiKey.getId();
bodyTrackHelper.persistChannelMapping(mapping);
BodyTrackHelper.ChannelStyle channelStyle = new BodyTrackHelper.ChannelStyle();
channelStyle.timespanStyles = new BodyTrackHelper.MainTimespanStyle();
channelStyle.timespanStyles.defaultStyle = new BodyTrackHelper.TimespanStyle();
channelStyle.timespanStyles.defaultStyle.fillColor = "#92da46";
channelStyle.timespanStyles.defaultStyle.borderColor = "#92da46";
channelStyle.timespanStyles.defaultStyle.borderWidth = 2;
channelStyle.timespanStyles.defaultStyle.top = 1.0;
channelStyle.timespanStyles.defaultStyle.bottom = 1.0;
channelStyle.timespanStyles.values = new HashMap();
GoogleCalendarConnectorSettings connectorSettings =
(GoogleCalendarConnectorSettings)settingsService.getConnectorSettings(apiKey.getId());
int n = calendarConfigs.size();
if (connectorSettings!=null) {
n = 0;
for (CalendarConfig calendar : connectorSettings.calendars) {
if (!calendar.hidden)
n++;
}
}
double rowHeight = 1.f/(n *2+1);
int i=0;
for (CalendarConfig config: calendarConfigs) {
if (connectorSettings!=null && config.hidden)
continue;
BodyTrackHelper.TimespanStyle stylePart = new BodyTrackHelper.TimespanStyle();
final int rowsFromTop = (i+1) * 2 - 1;
stylePart.top = (double)rowsFromTop*rowHeight-(rowHeight*0.25);
stylePart.bottom = stylePart.top+rowHeight+(rowHeight*0.25);
stylePart.fillColor = config.backgroundColor;
stylePart.borderColor = config.backgroundColor;
channelStyle.timespanStyles.values.put(config.id, stylePart);
i++;
}
bodyTrackHelper.setDefaultStyle(apiKey.getGuestId(), "google_calendar", "events", channelStyle);
}
private void loadHistory(UpdateInfo updateInfo, boolean incremental) throws Exception {
Calendar calendar = getCalendar(updateInfo.apiKey);
String pageToken = null;
long apiKeyId = updateInfo.apiKey.getId();
settingsService.getConnectorSettings(updateInfo.apiKey.getId());
List<String> existingCalendarIds = getExistingCalendarIds(apiKeyId);
List<CalendarListEntry> remoteCalendars = new ArrayList<CalendarListEntry>();
List<CalendarConfig> configs = new ArrayList<CalendarConfig>();
do {
final long then = System.currentTimeMillis();
final Calendar.CalendarList.List list = calendar.calendarList().list().setPageToken(pageToken);
final String query = list.getUriTemplate();
CalendarList calendarList = null;
try {
calendarList = list.execute();
countSuccessfulApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, query);
} catch (IOException e) {
countFailedApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, query, ExceptionUtils.getStackTrace(e),
list.getLastStatusCode(), list.getLastStatusMessage());
}
if (calendarList==null) throw new Exception("Could not get calendar list, apiKeyId=" + updateInfo.apiKey.getId());
List<CalendarListEntry> items = calendarList.getItems();
for (CalendarListEntry item : items) {
existingCalendarIds.remove(item.getId());
remoteCalendars.add(item);
configs.add(entry2Config(item));
}
pageToken = calendarList.getNextPageToken();
} while (pageToken != null);
initChannelMapping(updateInfo.apiKey, configs);
updateInfo.setContext(REMOTE_CALLENDARS_KEY, remoteCalendars);
for (CalendarListEntry remoteCalendar : remoteCalendars)
loadCalendarHistory(calendar, remoteCalendar, updateInfo, incremental);
deleteCalendars(apiKeyId, existingCalendarIds);
}
private CalendarConfig entry2Config(CalendarListEntry entry) {
CalendarConfig config = new CalendarConfig();
config.id = entry.getId();
config.foregroundColor = entry.getForegroundColor();
config.backgroundColor = entry.getBackgroundColor();
config.summary = entry.getSummary();
config.summaryOverride = entry.getSummaryOverride();
config.description = entry.getDescription();
config.primary = entry.getPrimary()!=null?entry.getPrimary():false;
return config;
}
private void deleteCalendars(final long apiKeyId, final List<String> existingCalendarIds) {
for (String existingCalendarId : existingCalendarIds) {
final String deleteQuery = "DELETE FROM Facet_GoogleCalendarEvent WHERE apiKeyId=" + apiKeyId + " AND calendarId='" + existingCalendarId + "'";
final long deleted = jpaDaoService.execute(deleteQuery);
System.out.println("deleted " + deleted + " events from calendar '" + existingCalendarId + "'");
}
}
private List<String> getExistingCalendarIds(long apiKeyId) {
List<String> l = (List<String>) jpaDaoService.executeNativeQuery("SELECT distinct calendarId from Facet_GoogleCalendarEvent WHERE apiKeyId=?", apiKeyId);
return l;
}
private void loadCalendarHistory(final Calendar calendar, final CalendarListEntry calendarEntry, final UpdateInfo updateInfo, final boolean incremental) throws IOException {
if (incremental) {
// first update existing events
// retrieve the last time we checked for updated events; if that attribute is null because the user had added the
// connector prior to this commit, we look up to the maximum allowed number of days in the past (20)
final String lastTimeString = guestService.getApiKeyAttribute(updateInfo.apiKey, LAST_TIME_WE_CHECKED_FOR_UPDATED_EVENTS_ATT);
Long since;
if (lastTimeString==null)
since = System.currentTimeMillis()-20*DateTimeConstants.MILLIS_PER_DAY;
else
since = Long.valueOf(lastTimeString);
// Store a conservative value for the last time we checked for updated events so that we
// don't miss updates that happen between the start of the check and when the check completes
String lastTimeCheckedForUpdatedEvents = String.valueOf(System.currentTimeMillis());
updateCalendarEvents(calendar, calendarEntry, updateInfo, since);
guestService.setApiKeyAttribute(updateInfo.apiKey, LAST_TIME_WE_CHECKED_FOR_UPDATED_EVENTS_ATT, lastTimeCheckedForUpdatedEvents);
// now fetch new events
String queryString = "select max(start) from Facet_GoogleCalendarEvent where apiKeyId=" + updateInfo.apiKey.getId() + " and calendarId='" + calendarEntry.getId() + "'";
since = jpaDaoService.executeNativeQuery(queryString);
if (since!=null)
loadCalendarEvents(calendar, calendarEntry, updateInfo, since);
} else {
loadCalendarEvents(calendar, calendarEntry, updateInfo, null);
}
}
private void updateCalendarEvents(final Calendar calendar,
final CalendarListEntry calendarEntry,
final UpdateInfo updateInfo,
long since) throws IOException {
// In the unlikely case where the server was down or disconnected more than 20 days and thus wasn't able to
// check for updated items during this period, we need to constrain the updatedMin parameter to a maximum
// of 20 days in the past, at the risk of getting an error from Google
since = Math.max(since, System.currentTimeMillis()-20* DateTimeConstants.MILLIS_PER_DAY);
String pageToken = null;
do {
long then = System.currentTimeMillis();
final Calendar.Events.List eventsApiCall = calendar.events().list(calendarEntry.getId());
final String uriTemplate = eventsApiCall.getUriTemplate();
try {
eventsApiCall.setPageToken(pageToken);
eventsApiCall.setShowHiddenInvitations(true);
eventsApiCall.setSingleEvents(true);
eventsApiCall.setTimeMax(new DateTime(System.currentTimeMillis()));
eventsApiCall.setUpdatedMin(new DateTime(since));
final Events events = eventsApiCall.execute();
countSuccessfulApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, uriTemplate);
final List<Event> eventList = events.getItems();
storeEvents(updateInfo, calendarEntry, eventList);
pageToken = events.getNextPageToken();
} catch (Throwable e) {
logger.warn("updateCalendarEvents unexpected httpCode=" +
eventsApiCall.getLastStatusCode() + " reason=" +
eventsApiCall.getLastStatusMessage() +
" since=" + since + " message=" + e.getMessage());
countFailedApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, uriTemplate, ExceptionUtils.getStackTrace(e),
eventsApiCall.getLastStatusCode(), eventsApiCall.getLastStatusMessage());
throw(new RuntimeException(e));
}
} while (pageToken != null);
}
private void loadCalendarEvents(final Calendar calendar,
final CalendarListEntry calendarEntry,
final UpdateInfo updateInfo,
final Long since) throws IOException {
String pageToken = null;
do {
long then = System.currentTimeMillis();
final Calendar.Events.List eventsApiCall = calendar.events().list(calendarEntry.getId());
final String uriTemplate = eventsApiCall.getUriTemplate();
try {
eventsApiCall.setPageToken(pageToken);
eventsApiCall.setShowHiddenInvitations(true);
eventsApiCall.setSingleEvents(true);
eventsApiCall.setTimeMax(new DateTime(System.currentTimeMillis()));
if (since!=null)
eventsApiCall.setTimeMin(new DateTime(since));
final Events events = eventsApiCall.execute();
countSuccessfulApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, uriTemplate);
final List<Event> eventList = events.getItems();
storeEvents(updateInfo, calendarEntry, eventList);
pageToken = events.getNextPageToken();
} catch (Throwable e) {
countFailedApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, uriTemplate, ExceptionUtils.getStackTrace(e),
eventsApiCall.getLastStatusCode(), eventsApiCall.getLastStatusMessage());
throw(new RuntimeException(e));
}
} while (pageToken != null);
}
private void storeEvents(final UpdateInfo updateInfo, final CalendarListEntry calendarEntry, final List<Event> eventList) throws Exception {
for (final Event event : eventList) {
createOrUpdateEvent(updateInfo, calendarEntry, event);
}
}
private void createOrUpdateEvent(final UpdateInfo updateInfo, final CalendarListEntry calendarEntry, final Event event) throws Exception {
if (event.getStatus().equalsIgnoreCase("cancelled")) {
System.out.println("event " + event.getSummary() + "/" + event.getDescription() + " was canceled");
final int deleted = jpaDaoService.execute(String.format("DELETE FROM Facet_GoogleCalendarEvent facet WHERE " +
"facet.apiKeyId=%s AND facet.googleId='%s'",
updateInfo.apiKey.getId(), event.getId()));
System.out.println("deleted " + deleted + " calendar entry");
return;
}
final String googleIdHash = hash(event.getId());
final String calendarIdHash = hash(calendarEntry.getId());
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery(
"e.apiKeyId=? AND (e.googleId=? OR e.googleId=?) AND (e.calendarId=? OR e.calendarId=?)",
updateInfo.apiKey.getId(),
event.getId(), googleIdHash,
calendarEntry.getId(), calendarIdHash);
final ApiDataService.FacetModifier<GoogleCalendarEventFacet> facetModifier = new ApiDataService.FacetModifier<GoogleCalendarEventFacet>() {
@Override
public GoogleCalendarEventFacet createOrModify(GoogleCalendarEventFacet facet, final Long apiKeyId) {
if (facet == null) {
facet = new GoogleCalendarEventFacet(updateInfo.apiKey.getId());
facet.googleId = event.getId().length()>250
? googleIdHash
:event.getId();
facet.guestId = updateInfo.apiKey.getGuestId();
facet.api = updateInfo.apiKey.getConnector().value();
facet.calendarId = calendarEntry.getId().length()>250
? calendarIdHash
: calendarEntry.getId();
}
facet.summary = event.getSummary();
facet.setCreated(event.getCreated());
facet.setAttendees(event.getAttendees());
facet.etag = event.getEtag();
facet.setStart(event.getStart());
facet.endTimeUnspecified = event.getEndTimeUnspecified();
facet.setEnd(event.getEnd());
facet.colorId = event.getColorId();
facet.setCreator(event.getCreator());
facet.description = event.getDescription();
facet.guestsCanSeeOtherGuests = event.getGuestsCanSeeOtherGuests();
facet.hangoutLink = event.getHangoutLink();
facet.htmlLink = event.getHtmlLink();
facet.iCalUID = event.getICalUID();
facet.kind = event.getKind();
facet.location = event.getLocation();
facet.locked = event.getLocked();
facet.setOrganizer(event.getOrganizer());
facet.setOriginalStartTime(event.getOriginalStartTime());
facet.status = event.getStatus();
facet.timeUpdated = System.currentTimeMillis();
facet.transparency = event.getTransparency();
facet.visibility = event.getVisibility();
facet.setRecurrence(event.getRecurrence());
if (event.getRecurringEventId()!=null) {
final String recurringEventIdHash = hash(event.getRecurringEventId());
facet.recurringEventId = event.getRecurringEventId().length()>250
? recurringEventIdHash
: event.getRecurringEventId();
}
facet.sequence = event.getSequence();
facet.setUpdated(event.getUpdated());
return facet;
}
};
// we could use the resulting value (facet) from this call if we needed to do further processing on it (e.g. passing it on to the datastore)
apiDataService.createOrReadModifyWrite(GoogleCalendarEventFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
}
@Override
public Object syncConnectorSettings(final UpdateInfo updateInfo, Object s) {
GoogleCalendarConnectorSettings settings = s == null
? new GoogleCalendarConnectorSettings()
: (GoogleCalendarConnectorSettings) s;
final List<CalendarListEntry> items = (List<CalendarListEntry>)updateInfo.getContext(REMOTE_CALLENDARS_KEY);
final List<CalendarConfig> configs = new ArrayList<CalendarConfig>();
for (CalendarListEntry calendarListEntry : items) {
final String calendarId = calendarListEntry.getId();
CalendarConfig config = settings.getCalendar(calendarId);
if (config==null) {
config = entry2Config(calendarListEntry);
settings.addCalendarConfig(config);
} else {
config.foregroundColor = calendarListEntry.getForegroundColor();
config.backgroundColor = calendarListEntry.getBackgroundColor();
config.summary = calendarListEntry.getSummary();
config.summaryOverride = calendarListEntry.getSummaryOverride();
config.description = calendarListEntry.getDescription();
config.primary = calendarListEntry.getPrimary()!=null?calendarListEntry.getPrimary():false;
}
configs.add(config);
}
initChannelMapping(updateInfo.apiKey, configs);
return settings;
}
private Calendar getCalendar(final ApiKey apiKey) throws UpdateFailedException {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
// Get all the attributes for this connector's oauth token from the stored attributes
final String accessToken = guestService.getApiKeyAttribute(apiKey, "accessToken");
final String refreshToken = guestService.getApiKeyAttribute(apiKey, "refreshToken");
final String clientId = guestService.getApiKeyAttribute(apiKey, "google.client.id");
final String clientSecret = guestService.getApiKeyAttribute(apiKey,"google.client.secret");
final GoogleCredential.Builder builder = new GoogleCredential.Builder();
builder.setTransport(httpTransport);
builder.setJsonFactory(jsonFactory);
builder.setClientSecrets(clientId, clientSecret);
GoogleCredential credential = builder.build();
final Long tokenExpires = Long.valueOf(guestService.getApiKeyAttribute(apiKey, "tokenExpires"));
credential.setExpirationTimeMilliseconds(tokenExpires);
credential.setAccessToken(accessToken);
credential.setRefreshToken(refreshToken);
try {
if (tokenExpires<System.currentTimeMillis()) {
boolean tokenRefreshed = false;
// Don't worry about checking if we are running on a mirrored test instance.
// Refreshing tokens independently on both the main server and a mirrored instance
// seems to work just fine.
// Try to swap the expired access token for a fresh one.
tokenRefreshed = credential.refreshToken();
if(tokenRefreshed) {
Long newExpireTime = credential.getExpirationTimeMilliseconds();
logger.info("google calendar token has been refreshed, new expire time = " + newExpireTime);
// Update stored expire time
guestService.setApiKeyAttribute(apiKey, "accessToken", credential.getAccessToken());
guestService.setApiKeyAttribute(apiKey, "tokenExpires", newExpireTime.toString());
}
}
}
catch (TokenResponseException e) {
logger.warn("module=GoogleCalendarUpdater component=background_updates action=refreshToken" +
" connector=" + apiKey.getConnector().getName() + " guestId=" + apiKey.getGuestId() + " status=permanently failed");
// Notify the user that the tokens need to be manually renewed
notificationsService.addNamedNotification(apiKey.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(),
"Heads Up. We failed in our attempt to automatically refresh your Google Calendar authentication tokens.<br>" +
"Please head to <a href=\"javascript:App.manageConnectors()\">Manage Connectors</a>,<br>" +
"scroll to the Google Calendar connector, and renew your tokens (look for the <i class=\"icon-resize-small icon-large\"></i> icon)");
// Record permanent update failure since this connector is never
// going to succeed
guestService.setApiKeyStatus(apiKey.getId(), ApiKey.Status.STATUS_PERMANENT_FAILURE, Utils.stackTrace(e), ApiKey.PermanentFailReason.NEEDS_REAUTH);
throw new UpdateFailedException("refresh token attempt permanently failed due to a bad token refresh response", e, true, ApiKey.PermanentFailReason.NEEDS_REAUTH);
}
catch (IOException e) {
logger.warn("module=GoogleCalendarUpdater component=background_updates action=refreshToken" +
" connector=" + apiKey.getConnector().getName() + " guestId=" + apiKey.getGuestId() + " status=permanently failed");
// Notify the user that the tokens need to be manually renewed
throw new UpdateFailedException("refresh token attempt failed", e, true, ApiKey.PermanentFailReason.NEEDS_REAUTH);
}
final Calendar.Builder calendarBuilder = new Calendar.Builder(httpTransport, jsonFactory, credential);
final Calendar calendar = calendarBuilder.build();
return calendar;
}
@Override
public void syncSharedConnectorSettings(final long apiKeyId, final SharedConnector sharedConnector) {
JSONObject jsonSettings = new JSONObject();
if (sharedConnector.filterJson!=null)
jsonSettings = JSONObject.fromObject(sharedConnector.filterJson);
// get calendars, add new configs for new calendars...
// we use the data in the connector settings, which have either just been synched (see UpdateWorker's syncSettings)
// or were synched when the connector was last updated; in either cases, we know that the data is up-to-date
final GoogleCalendarConnectorSettings connectorSettings = (GoogleCalendarConnectorSettings)settingsService.getConnectorSettings(apiKeyId);
final List<CalendarConfig> calendars = connectorSettings.calendars;
JSONArray sharingSettingsCalendars = new JSONArray();
if (jsonSettings.has("calendars"))
sharingSettingsCalendars = jsonSettings.getJSONArray("calendars");
there: for (CalendarConfig calendarConfig : calendars) {
for (int i=0; i<sharingSettingsCalendars.size(); i++) {
JSONObject sharingSettingsCalendar = sharingSettingsCalendars.getJSONObject(i);
if (sharingSettingsCalendar.getString("id").equals(calendarConfig.id))
continue there;
}
JSONObject sharingConfig = new JSONObject();
sharingConfig.accumulate("id", calendarConfig.id);
sharingConfig.accumulate("summary", calendarConfig.summary);
sharingConfig.accumulate("description", calendarConfig.description);
sharingConfig.accumulate("shared", false);
sharingSettingsCalendars.add(sharingConfig);
}
// and remove configs for deleted notebooks - leave others untouched
JSONArray settingsToDelete = new JSONArray();
there: for (int i=0; i<sharingSettingsCalendars.size(); i++) {
JSONObject sharingSettingsCalendar = sharingSettingsCalendars.getJSONObject(i);
for (CalendarConfig calendarConfig : calendars) {
if (sharingSettingsCalendar.getString("id").equals(calendarConfig.id))
continue there;
}
settingsToDelete.add(sharingSettingsCalendar);
}
for (int i=0; i<settingsToDelete.size(); i++) {
JSONObject toDelete = settingsToDelete.getJSONObject(i);
for (int j=0; j<sharingSettingsCalendars.size(); j++) {
if (sharingSettingsCalendars.getJSONObject(j).getString("id").equals(toDelete.getString("id"))) {
sharingSettingsCalendars.remove(j);
}
}
}
jsonSettings.put("calendars", sharingSettingsCalendars);
String toPersist = jsonSettings.toString();
buddiesService.setSharedConnectorFilter(sharedConnector.getId(), toPersist);
}
}