Skip to content

Commit

Permalink
Google Calendar Integration (#192)
Browse files Browse the repository at this point in the history
* Maven Google Calendar SDK

* SyncManager

* Create new classes

* Sync command

* Add more gradle dependencies

* Create Sync & SyncProvider interfaces

* ModelManager supports Sync

* Sync methods in Model

* Add Sync to parser

* SyncManager

* Sync provider Google

* Google stuff

* Revert bad merge change

* Basic event adding hooks workings

* tag code

* CRUD support

* Cleanup

* Sync on/off is now persisted using credential file

* Multithreading and concurrency

* Multithreading and concurrency support in SyncProvider made it trival to support adding add tasks when Sync is turned on

* Small cleanup

* Add mockito dependency

* SyncManagerTests

* Testing sync on/off

* Remove sync on

* tag syncmanagertest

* Simple testing in GoogleTests

* Add invalid sync

* deleteCalendadrMethod

* Much more Google calendar testing

* test sync on too

* tag code

* Add comments
  • Loading branch information
burnflare authored Nov 6, 2016
1 parent ceff169 commit cef6251
Show file tree
Hide file tree
Showing 13 changed files with 760 additions and 2 deletions.
Binary file added StoredCredentialForTesting
Binary file not shown.
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ allprojects {
monocleVersion = '1.8.0_20'
nattyVersion = '0.12'
reflectionsVersion = '0.9.10'
googleCalendarVersion = 'v3-rev220-1.22.0'
googleHttpVersion = '1.21.0'
googleOauthVersion = '1.21.0'
mockitoVersion = '1.+'


libDir = 'lib'
}
Expand All @@ -56,6 +61,10 @@ allprojects {
compile "com.google.guava:guava:$guavaVersion"
compile "com.joestelmach:natty:$nattyVersion"
compile "org.reflections:reflections:$reflectionsVersion"
compile "com.google.apis:google-api-services-calendar:$googleCalendarVersion"
compile "com.google.http-client:google-http-client-jackson2:$googleHttpVersion"
compile "com.google.oauth-client:google-oauth-client-jetty:$googleOauthVersion"
compile "org.mockito:mockito-core:$mockitoVersion"

testCompile "junit:junit:$junitVersion"
testCompile "org.testfx:testfx-core:$testFxVersion"
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/seedu/agendum/logic/commands/SyncCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package seedu.agendum.logic.commands;

import seedu.agendum.commons.exceptions.IllegalValueException;

public class SyncCommand extends Command {
// COMMAND_WORD, COMMAND_FORMAT, COMMAND_DESCRIPTION are for display in help window
public static final String COMMAND_WORD = "sync";
private static final String COMMAND_FORMAT = "sync <on/off>";
private static final String COMMAND_DESCRIPTION = "Turn syncing on or off";
private static final String MESSAGE_USAGE = COMMAND_WORD + "- "
+ COMMAND_DESCRIPTION;

public static final String SYNC_ON_MESSAGE = "Google Calendar Sync is on";
public static final String SYNC_OFF_MESSAGE = "Google Calendar Sync is off";

public static final String MESSAGE_WRONG_OPTION = "Invalid option for sync.";

private boolean syncOption;

//@@author A0003878Y
/**
* Convenience constructor using name
*
* @throws IllegalValueException if any of the raw values are invalid
*/
public SyncCommand(String option) throws IllegalValueException {

if (option.trim().equalsIgnoreCase("on")) {
syncOption = true;
} else if (option.trim().equalsIgnoreCase("off")) {
syncOption = false;
} else {
throw new IllegalValueException(MESSAGE_WRONG_OPTION);
}
}

@Override
public CommandResult execute() {
if (syncOption) {
model.activateModelSyncing();
return new CommandResult(SYNC_ON_MESSAGE);
} else {
model.deactivateModelSyncing();
return new CommandResult(SYNC_OFF_MESSAGE);
}
}

//@@author
public static String getName() {
return COMMAND_WORD;
}

public static String getFormat() {
return COMMAND_FORMAT;
}

public static String getDescription() {
return COMMAND_DESCRIPTION;
}
}
23 changes: 22 additions & 1 deletion src/main/java/seedu/agendum/logic/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ public Command parseCommand(String userInput) {
case LoadCommand.COMMAND_WORD :
return new LoadCommand(arguments);

case SyncCommand.COMMAND_WORD: {
return prepareSync(arguments);
}

default:
//@@author A0003878Y
Optional<String> alternativeCommand = EditDistanceCalculator.closestCommandMatch(commandWord);
Expand Down Expand Up @@ -413,7 +417,8 @@ private Set<Integer> parseIndexes(String args) {
}

return taskIds;
}
}

//@@author

/**
Expand All @@ -435,4 +440,20 @@ private Command prepareFind(String args) {
return new FindCommand(keywordSet);
}

//@@author A0003878Y
/**
* Parses arugments in the context of the sync command.
*
* @param args full command args string
* @return the prepared command
*/
private Command prepareSync(String args) {
try {
return new SyncCommand(args);
} catch (IllegalValueException ive) {
return new IncorrectCommand(ive.getMessage());
}
}

//@@author
}
6 changes: 6 additions & 0 deletions src/main/java/seedu/agendum/model/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,10 @@ void unmarkTasks(List<ReadOnlyTask> targets)
/** Updates the current todolist to the loaded data**/
void handleLoadDataCompleteEvent(LoadDataCompleteEvent event);

/** Turns on model syncing using to a thirds party syncing provider **/
void activateModelSyncing();

/** Turns off model syncing **/
void deactivateModelSyncing();

}
40 changes: 39 additions & 1 deletion src/main/java/seedu/agendum/model/ModelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import java.util.logging.Logger;

import com.google.common.eventbus.Subscribe;
import seedu.agendum.sync.Sync;
import seedu.agendum.sync.SyncManager;
import seedu.agendum.sync.SyncProviderGoogle;

/**
* Represents the in-memory model of the to do list data.
Expand All @@ -35,6 +38,8 @@ public class ModelManager extends ComponentManager implements Model {
private final FilteredList<Task> filteredTasks;
private final SortedList<Task> sortedTasks;

private final SyncManager syncManager;

//@@author A0133367E
/**
* Signals that an operation to remove a list from the stack of previous lists would fail
Expand All @@ -59,6 +64,8 @@ public ModelManager(ToDoList src, UserPrefs userPrefs) {
sortedTasks = filteredTasks.sorted();
previousLists = new Stack<ToDoList>();
backupCurrentToDoList();

syncManager = new SyncManager(new SyncProviderGoogle());
}

public ModelManager() {
Expand All @@ -71,6 +78,8 @@ public ModelManager(ReadOnlyToDoList initialData) {
sortedTasks = filteredTasks.sorted();
previousLists = new Stack<ToDoList>();
backupCurrentToDoList();

syncManager = new SyncManager(new SyncProviderGoogle());
}

//@@author A0133367E
Expand Down Expand Up @@ -111,6 +120,9 @@ private void indicateLoadDataRequest(String location) {
public synchronized void deleteTasks(List<ReadOnlyTask> targets) throws TaskNotFoundException {
for (ReadOnlyTask target: targets) {
toDoList.removeTask(target);

// Delete tasks in sync manager
syncManager.deleteEvent((Task) target);
}

logger.fine("[MODEL] --- successfully deleted all specified targets from the to-do list");
Expand All @@ -126,6 +138,8 @@ public synchronized void addTask(Task task) throws UniqueTaskList.DuplicateTaskE
backupCurrentToDoList();
updateFilteredListToShowAll();
indicateToDoListChanged();

syncManager.addNewEvent(task);
}

@Override
Expand All @@ -137,6 +151,10 @@ public synchronized void updateTask(ReadOnlyTask target, Task updatedTask)
backupCurrentToDoList();
updateFilteredListToShowAll();
indicateToDoListChanged();

// Delete old task and add new task
syncManager.deleteEvent((Task) target);
syncManager.addNewEvent(updatedTask);
}

@Override
Expand Down Expand Up @@ -226,8 +244,28 @@ public synchronized void loadFromLocation(String location) {
indicateChangeSaveLocation(location);
indicateLoadDataRequest(location);
}
//@@author A0003878Y

//=========== Sync Methods ===============================================================================

@Override
public void activateModelSyncing() {
if (syncManager.getSyncStatus() != Sync.SyncStatus.RUNNING) {
syncManager.startSyncing();

// Add all current events into sync provider
toDoList.getTasks().forEach(syncManager::addNewEvent);
}
}

@Override
public void deactivateModelSyncing() {
if (syncManager.getSyncStatus() != Sync.SyncStatus.NOTRUNNING) {
syncManager.stopSyncing();
}
}

//@@author

//=========== Filtered Task List Accessors ===============================================================

@Override
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/seedu/agendum/sync/Sync.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package seedu.agendum.sync;

import seedu.agendum.model.task.Task;

//@@author A0003878Y
public interface Sync {

/** Enum used to persist SyncManager status **/
enum SyncStatus {
RUNNING, NOTRUNNING
}

/** Retrieve sync manager sync status **/
SyncStatus getSyncStatus();

/** Sets sync manager sync status **/
void setSyncStatus(SyncStatus syncStatus);

/** Turn on syncing **/
void startSyncing();

/** Turn off syncing **/
void stopSyncing();

/** Add Task to sync provider **/
void addNewEvent(Task task);

/** Remove task from sync provider **/
void deleteEvent(Task task);
}
58 changes: 58 additions & 0 deletions src/main/java/seedu/agendum/sync/SyncManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package seedu.agendum.sync;

import seedu.agendum.commons.core.ComponentManager;
import seedu.agendum.commons.core.LogsCenter;
import seedu.agendum.model.task.Task;

import java.util.logging.Logger;

//@@author A0003878Y
public class SyncManager extends ComponentManager implements Sync {
private final Logger logger = LogsCenter.getLogger(SyncManager.class);
private SyncStatus syncStatus = SyncStatus.NOTRUNNING;

private final SyncProvider syncProvider;

public SyncManager(SyncProvider syncProvider) {
this.syncProvider = syncProvider;
this.syncProvider.setManager(this);

syncProvider.startIfNeeded();
}

@Override
public SyncStatus getSyncStatus() {
return syncStatus;
}

@Override
public void setSyncStatus(SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}

@Override
public void startSyncing() {
syncProvider.start();
}

@Override
public void stopSyncing() {
syncProvider.stop();
}

@Override
public void addNewEvent(Task task) {
if (syncStatus == SyncStatus.RUNNING) {
if (task.getStartDateTime().isPresent() && task.getEndDateTime().isPresent()) {
syncProvider.addNewEvent(task);
}
}
}

@Override
public void deleteEvent(Task task) {
if (syncStatus == SyncStatus.RUNNING) {
syncProvider.deleteEvent(task);
}
}
}
31 changes: 31 additions & 0 deletions src/main/java/seedu/agendum/sync/SyncProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package seedu.agendum.sync;

import seedu.agendum.model.task.Task;

//@@author A0003878Y
public abstract class SyncProvider {

/** Sync provider's keep a reference to the manager so that they can set it's
* sync status **/
protected Sync syncManager;

/** Start sync provider and perform initialization **/
public abstract void start();

/** Start sync provider if it needs to be started **/
public abstract void startIfNeeded();

/** Stop sync provider and perform cleanup **/
public abstract void stop();

/** Add event into sync provider **/
public abstract void addNewEvent(Task task);

/** Delete event from sync provider **/
public abstract void deleteEvent(Task task);

/** Set sync provider's sync manager **/
public void setManager(Sync syncManager) {
this.syncManager = syncManager;
}
}
Loading

0 comments on commit cef6251

Please sign in to comment.