com.licensetokil.atypistcalendar.gcal.SyncManager.java Source code

Java tutorial

Introduction

Here is the source code for com.licensetokil.atypistcalendar.gcal.SyncManager.java

Source

//@author A0097142E
package com.licensetokil.atypistcalendar.gcal;

import java.io.IOException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.licensetokil.atypistcalendar.parser.AddGoogleAction;
import com.licensetokil.atypistcalendar.tasksmanager.Task;

class SyncManager {

    protected static final Calendar REMOTE_TODO_START_END_DATE = Calendar.getInstance();
    protected static final JsonArray REMOTE_TODO_RECURRENCE_PROPERTY = new JsonArray();

    private static final String QUICK_ADD_RESOURCE_LABEL_TEXT = "text";
    private static final String QUICK_ADD_RESOURCE_LABEL_CALENDAR_ID = "calendarId";

    private static final String GOOGLE_REQUEST_URL_QUICK_ADD = "https://www.googleapis.com/calendar/v3/calendars/%s/events/quickAdd";

    private static final String ERROR_MESSAGE_QUICK_ADD_UNABLE_TO_CONNECT_TO_GOOGLE = "A Typist's Calendar is unable to connect to Google. Please try again in a little while.";

    private static final String MESSAGE_QUICK_ADD_SUCCESSFUL = "Added: \n%s";

    private static final String EVENT_RESOURCE_LABEL_UPDATED = "updated";

    private static SyncManager instance = null;

    private static Logger logger = Logger.getLogger(SyncManager.class.getName());

    private Lock goToSleepLock = new ReentrantLock();
    private Condition goToSleepCondition = goToSleepLock.newCondition();

    private PriorityBlockingQueue<SyncNode> syncQueue;
    private Syncer syncer;
    private String remoteCalendarId;

    static {
        REMOTE_TODO_START_END_DATE.set(2013, Calendar.JANUARY, 1);
        REMOTE_TODO_RECURRENCE_PROPERTY.add(new JsonPrimitive("RRULE:FREQ=MONTHLY"));
    }

    private SyncManager() {
        syncQueue = new PriorityBlockingQueue<SyncNode>();
        syncer = null;
        remoteCalendarId = null;
    }

    protected static SyncManager getInstance() {
        if (instance == null) {
            instance = new SyncManager();
        }
        return instance;
    }

    protected void initialiseRemoteCalendar() {
        syncQueue.add(new InitialiseRemoteCalendarSyncNode());
        runSyncer();
    }

    protected void addRemoteTask(Task localTask) {
        syncQueue.add(new AddSyncNode(localTask));
        runSyncer();
    }

    protected void updateRemoteTask(Task localTask, String remoteTaskId) {
        deleteRemoteTask(remoteTaskId);
        addRemoteTask(localTask);
        //TODO temporary measure, cause increasing the iCal sequence number is difficult - see Dev's guide
        //queue.add(new UpdateSyncNode(localTask, remoteTaskID));
        runSyncer();
    }

    protected void deleteRemoteTask(String remoteTaskId) {
        syncQueue.add(new DeleteSyncNode(remoteTaskId));
        runSyncer();
    }

    protected void doCompleteSync() {
        syncQueue.add(new DoCompleteSyncNode());
        runSyncer();
    }

    protected PriorityBlockingQueue<SyncNode> getSyncQueue() {
        return syncQueue;
    }

    protected String getRemoteCalendarId() {
        return remoteCalendarId;
    }

    protected void setRemoteCalendarId(String remoteCalendarId) {
        this.remoteCalendarId = remoteCalendarId;
    }

    protected Lock getGoToSleepLock() {
        return goToSleepLock;
    }

    protected Condition getGoToSleepCondition() {
        return goToSleepCondition;
    }

    protected String executeAddGoogleAction(AddGoogleAction action) {
        HashMap<String, String> formParameters = new HashMap<>();

        formParameters.put(QUICK_ADD_RESOURCE_LABEL_CALENDAR_ID, getRemoteCalendarId());
        formParameters.put(QUICK_ADD_RESOURCE_LABEL_TEXT, action.getUserInput());

        JsonObject serverReply;
        try {
            serverReply = Utilities.parseToJsonObject(Utilities.sendUrlencodedFormHttpsRequest(
                    String.format(GOOGLE_REQUEST_URL_QUICK_ADD, getRemoteCalendarId()),
                    Utilities.REQUEST_METHOD_POST, AuthenticationManager.getInstance().getAuthorizationHeader(),
                    formParameters));
        } catch (JsonParseException | IllegalStateException | IOException e) {
            logger.warning("Unable to connection to Google; exception thrown - " + e.getMessage());
            return ERROR_MESSAGE_QUICK_ADD_UNABLE_TO_CONNECT_TO_GOOGLE;
        }

        return String.format(MESSAGE_QUICK_ADD_SUCCESSFUL,
                insertRemoteTaskIntoTasksManager(serverReply).outputStringForDisplay());
    }

    protected Task insertRemoteTaskIntoTasksManager(JsonObject remoteTask) {
        //Extracting the (common) fields from the RemoteTask object.
        String remoteTaskId = Utilities.getJsonObjectValueOrEmptyString(remoteTask, "id");
        String description = Utilities.getJsonObjectValueOrEmptyString(remoteTask, "summary");
        String location = Utilities.getJsonObjectValueOrEmptyString(remoteTask, "location");
        Calendar lastModifiedDate = getLastModifiedDateOrTimeNow(remoteTask);

        Task newLocalTask = null;
        if (remoteTask.getAsJsonObject("start").get("date") != null) {
            //Task type is a todo
            newLocalTask = GoogleCalendarManager.getInstance().insertLocalTaskIntoTasksManager(description,
                    location, lastModifiedDate, remoteTaskId);
        } else {
            Calendar startTime = null;
            Calendar endTime = null;
            try {
                startTime = Utilities.parseGoogleDateTimeObject(remoteTask.getAsJsonObject("start"));
                endTime = Utilities.parseGoogleDateTimeObject(remoteTask.getAsJsonObject("end"));
            } catch (ParseException e) {
                logger.severe(
                        "Unable to parse Google DateTime object (this is unexpected as Google only returns a standardised format)");
                e.printStackTrace();
                return null;
            }

            boolean startTimeIsTheSameAsEndTime = startTime.compareTo(endTime) == 0;
            if (startTimeIsTheSameAsEndTime) {
                //Task type is a deadline
                newLocalTask = GoogleCalendarManager.getInstance().insertLocalTaskIntoTasksManager(description,
                        location, lastModifiedDate, remoteTaskId, endTime);
            } else {
                //Task type is a schedule
                newLocalTask = GoogleCalendarManager.getInstance().insertLocalTaskIntoTasksManager(description,
                        location, lastModifiedDate, remoteTaskId, endTime, startTime);
            }
        }

        //We update the remote task so it gets the local task's ID gets recorded remotely.
        SyncManager.getInstance().updateRemoteTask(newLocalTask, remoteTaskId);
        return newLocalTask;
    }

    protected static Calendar getLastModifiedDateOrTimeNow(JsonObject remoteTask) {
        try {
            return Utilities.parseGenericGoogleDateString(
                    Utilities.getJsonObjectValueOrEmptyString(remoteTask, EVENT_RESOURCE_LABEL_UPDATED),
                    Utilities.RFC3339_FORMAT_WITH_MILLISECONDS);
        } catch (ParseException e) {
            return Calendar.getInstance(); // Fail quietly
        }
    }

    private void runSyncer() {
        if (!AuthenticationManager.getInstance().isAuthenticated()) {
            logger.info(
                    "User is not logged in. No use running Syncer as it will return will authentication errors. Nothing else to do, returning. (Logging in will tigger this process again.");
            return;
        }

        if (syncer == null || !syncer.isAlive()) {
            syncer = new Syncer();
            syncer.start();
        } else {
            //Signaling... if needed.
            goToSleepLock.lock();
            goToSleepCondition.signal();
            goToSleepLock.unlock();
        }
    }

}