Skip to content

Commit

Permalink
add new app created with picocli. uses batching. support for touching…
Browse files Browse the repository at this point in the history
… events for now.
  • Loading branch information
thomasmhofmann committed Jul 10, 2019
1 parent d74844f commit 9feb87d
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ hs_err_pid*
**/credentials.json
tokens
.settings/org.eclipse.buildship.core.prefs
build
11 changes: 9 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "GoogleCalendarTools (Launch) Action - Touch",
"request": "launch",
"mainClass": "de.randombits.google.calendar.GoogleCalendarTools",
"args": "-a=touch -s=2006-07-29 -e=2009-12-31"
},
{
"type": "java",
"name": "CodeLens (Launch) - TouchOldEntries",
Expand All @@ -24,12 +31,12 @@
"request": "launch",
"mainClass": "de.randombits.google.calendar.DeleteCSVConvertDuplicates",
"projectName": "google-calendar-cleanup"
}
},
{
"type": "java",
"name": "Debug (Launch) - Current File",
"request": "launch",
"mainClass": "${file}"
},
}
]
}
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id 'com.github.johnrengelman.shadow' version '5.1.0'
}

apply plugin: 'java'
apply plugin: 'application'

Expand All @@ -14,4 +18,5 @@ dependencies {
compile 'com.google.api-client:google-api-client:1.23.0'
compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0'
compile 'com.google.apis:google-api-services-calendar:v3-rev305-1.23.0'
compile 'info.picocli:picocli:4.0.0-beta-2'
}
208 changes: 208 additions & 0 deletions src/main/java/de/randombits/google/calendar/GoogleCalendarTools.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package de.randombits.google.calendar;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.batch.BatchRequest;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.DateTime;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.calendar.Calendar;
import com.google.api.services.calendar.CalendarScopes;
import com.google.api.services.calendar.model.Event;
import com.google.api.services.calendar.model.Events;

import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@Command(name = "GoogleCalendarTools", mixinStandardHelpOptions = true, version = "1.0", description = "Carries out different actions against the Google Calendar API.")
public class GoogleCalendarTools implements Callable<Integer> {

private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String APPLICATION_NAME = "Google Calendar Tools";
private static final List<String> SCOPES = Collections.singletonList(CalendarScopes.CALENDAR);
private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
private static final String TOKENS_DIRECTORY_PATH = "tokens";

enum Action {
touch, cleanByKeyword
}

@Option(names = { "-a",
"--action" }, required = true, paramLabel = "ACTION", description = "The action to run. Valid values are: : ${COMPLETION-CANDIDATES}")
Action action;

@Option(names = { "-c",
"--calendar" }, paramLabel = "CALENDAR_ID", description = "The ID of the calendar to run actions against. Default is ${DEFAULT-VALUE}")
String calendarId = "primary";

@Option(names = { "-b",
"--batchsize" }, paramLabel = "BATCH_SIZE", description = "The number of events to process in one API call. Default is ${DEFAULT-VALUE}")
int batchSize = 10;

@Option(names = { "-i",
"--interval" }, paramLabel = "REQUEST_INTERVAL", description = "Time to wait between API calls. Default is ${DEFAULT-VALUE}")
Duration requestInterval = Duration.ofSeconds(3);

@ArgGroup(exclusive = false, multiplicity = "1")
DateRange dateRange;

static class DateRange {
@Option(names = { "-s",
"--startDate" }, required = true, paramLabel = "START_DATE", description = "Specify the START_DATE. The action will be run for all events between the START_DATE and END_DATE.")
Date startDate;

@Option(names = { "-e",
"--endDate" }, required = true, paramLabel = "END_DATE", description = "Specify the END_DATE. The action will be run for all events between the START_DATE and END_DATE.")
Date endDate;
}

private Calendar service;
private String pageToken;

public static void main(String[] args) {
int exitCode = new CommandLine(new GoogleCalendarTools()).execute(args);
System.exit(exitCode);
}

public GoogleCalendarTools() {
try {
NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
this.service = new Calendar.Builder(httpTransport, JSON_FACTORY, getCredentials(httpTransport))
.setApplicationName(APPLICATION_NAME).build();
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}

private Credential getCredentials(NetHttpTransport netHttpTransport) {
InputStream in = GoogleCalendarTools.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new RuntimeException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
try {
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(netHttpTransport, JSON_FACTORY,
clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline").build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public Integer call() throws Exception {
switch (this.action) {
case touch:
System.out.println("Running Touch");
do {
Map<String, Event> toTouch = findEventsToTouch(service, calendarId);
touchEvents(service, toTouch);
} while (this.pageToken != null);

System.out.println("No more events to touch.");
break;
case cleanByKeyword:
System.out.println("Running Clean");
break;

default:
break;
}
return 0;
}

private Map<String, Event> findEventsToTouch(Calendar service, String calendarId) {
Map<String, Event> toTouch = new HashMap<>();
Events events;
try {
/* use the half of the batch size because two updates are needed to touch them */
Calendar.Events.List request = service.events().list(calendarId).setMaxResults(this.batchSize / 2)
.setTimeMin(new DateTime(dateRange.startDate)).setTimeMax(new DateTime(dateRange.endDate))
.setPageToken(this.pageToken).setOrderBy("startTime").setSingleEvents(true);

events = request.execute();

List<Event> items = events.getItems();
for (Event event : items) {
toTouch.put(event.getId(), event);
System.out.printf("%s (%s) (%s)\n", event.getSummary(), getStart(event), event.getId());
}
this.pageToken = events.getNextPageToken();
System.out.printf("* Found %s entries to touch.\n", toTouch.size());
return toTouch;
} catch (NumberFormatException | IOException e) {
throw new RuntimeException(e);
}
}

private void touchEvents(Calendar service, Map<String, Event> eventsToTouch) {
JsonBatchCallback<Event> batchCallback = new JsonBatchCallback<Event>() {
public void onSuccess(Event event, HttpHeaders responseHeaders) {
}

public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
throw new RuntimeException("API Call failed due to: " + e);
}
};

BatchRequest batchRequest = service.batch();

try {
for (Entry<String, Event> eventById : eventsToTouch.entrySet()) {
Event event = eventById.getValue();
String eventId = eventById.getKey();

Event patch = new Event();
patch.setSummary(event.getSummary() + " touch");
System.out.printf("Touching %s %s (%s)\n", event.getSummary(), getStart(event), eventId);
service.events().patch(calendarId, eventId, patch).queue(batchRequest, batchCallback);
patch.setSummary(event.getSummary());
service.events().patch(calendarId, eventId, patch).queue(batchRequest, batchCallback);
}
batchRequest.execute();
System.out.printf("* Sleeping for %d seconds to avoid rate limit being overrun.\n", this.requestInterval.getSeconds());
Thread.sleep(this.requestInterval.toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (IOException e) {
throw new RuntimeException(e);
}
}


private DateTime getStart(Event event) {
DateTime start = event.getStart().getDateTime();
if (start == null) {
start = event.getStart().getDate();
}
return start;
}

}

0 comments on commit 9feb87d

Please sign in to comment.