Convert GoogleTaskSynchronizer to Kotlin

pull/1051/head
Alex Baker 4 years ago
parent 0146cd5766
commit 90571eca35

@ -1,498 +0,0 @@
package org.tasks.gtasks;
import static com.google.common.collect.Lists.transform;
import static org.tasks.Strings.isNullOrEmpty;
import static org.tasks.date.DateTimeUtils.newDateTime;
import android.content.Context;
import androidx.annotation.Nullable;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.tasks.model.TaskList;
import com.google.api.services.tasks.model.TaskLists;
import com.google.api.services.tasks.model.Tasks;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.dao.TaskDaoBlocking;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.api.HttpNotFoundException;
import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter;
import dagger.hilt.android.qualifiers.ApplicationContext;
import java.io.EOFException;
import java.io.IOException;
import java.net.HttpRetryException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import javax.net.ssl.SSLException;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.analytics.Firebase;
import org.tasks.billing.Inventory;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDaoBlocking;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDaoBlocking;
import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import timber.log.Timber;
public class GoogleTaskSynchronizer {
private static final String DEFAULT_LIST = "@default"; // $NON-NLS-1$
private static final int MAX_TITLE_LENGTH = 1024;
private static final int MAX_DESCRIPTION_LENGTH = 8192;
private static final Comparator<com.google.api.services.tasks.model.Task> PARENTS_FIRST =
(o1, o2) -> {
if (isNullOrEmpty(o1.getParent())) {
return isNullOrEmpty(o2.getParent()) ? 0 : -1;
} else {
return isNullOrEmpty(o2.getParent()) ? 1 : 0;
}
};
private final Context context;
private final GoogleTaskListDaoBlocking googleTaskListDao;
private final GtasksListService gtasksListService;
private final Preferences preferences;
private final TaskDaoBlocking taskDao;
private final Firebase firebase;
private final GoogleTaskDaoBlocking googleTaskDao;
private final TaskCreator taskCreator;
private final DefaultFilterProvider defaultFilterProvider;
private final PermissionChecker permissionChecker;
private final GoogleAccountManager googleAccountManager;
private final LocalBroadcastManager localBroadcastManager;
private final Inventory inventory;
private final TaskDeleter taskDeleter;
private final GtasksInvoker gtasksInvoker;
@Inject
public GoogleTaskSynchronizer(
@ApplicationContext Context context,
GoogleTaskListDaoBlocking googleTaskListDao,
GtasksListService gtasksListService,
Preferences preferences,
TaskDaoBlocking taskDao,
Firebase firebase,
GoogleTaskDaoBlocking googleTaskDao,
TaskCreator taskCreator,
DefaultFilterProvider defaultFilterProvider,
PermissionChecker permissionChecker,
GoogleAccountManager googleAccountManager,
LocalBroadcastManager localBroadcastManager,
Inventory inventory,
TaskDeleter taskDeleter,
GtasksInvoker gtasksInvoker) {
this.context = context;
this.googleTaskListDao = googleTaskListDao;
this.gtasksListService = gtasksListService;
this.preferences = preferences;
this.taskDao = taskDao;
this.firebase = firebase;
this.googleTaskDao = googleTaskDao;
this.taskCreator = taskCreator;
this.defaultFilterProvider = defaultFilterProvider;
this.permissionChecker = permissionChecker;
this.googleAccountManager = googleAccountManager;
this.localBroadcastManager = localBroadcastManager;
this.inventory = inventory;
this.taskDeleter = taskDeleter;
this.gtasksInvoker = gtasksInvoker;
}
static void mergeDates(long remoteDueDate, Task local) {
if (remoteDueDate > 0 && local.hasDueTime()) {
DateTime oldDate = newDateTime(local.getDueDate());
DateTime newDate =
newDateTime(remoteDueDate)
.withHourOfDay(oldDate.getHourOfDay())
.withMinuteOfHour(oldDate.getMinuteOfHour())
.withSecondOfMinute(oldDate.getSecondOfMinute());
local.setDueDateAdjustingHideUntil(
Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, newDate.getMillis()));
} else {
local.setDueDateAdjustingHideUntil(remoteDueDate);
}
}
public void sync(GoogleTaskAccount account, int i) {
Timber.d("%s: start sync", account);
try {
if (i == 0 || inventory.hasPro()) {
synchronize(account);
} else {
account.setError(context.getString(R.string.requires_pro_subscription));
}
} catch (SocketTimeoutException
| SSLException
| SocketException
| UnknownHostException
| HttpRetryException
| EOFException e) {
Timber.e(e);
account.setError(e.getMessage());
} catch (GoogleJsonResponseException e) {
account.setError(e.getMessage());
if (e.getStatusCode() == 401) {
Timber.e(e);
} else {
firebase.reportException(e);
}
} catch (Exception e) {
account.setError(e.getMessage());
firebase.reportException(e);
} finally {
googleTaskListDao.update(account);
localBroadcastManager.broadcastRefreshList();
Timber.d("%s: end sync", account);
}
}
private void synchronize(GoogleTaskAccount account) throws IOException {
if (!permissionChecker.canAccessAccounts()
|| googleAccountManager.getAccount(account.getAccount()) == null) {
account.setError(context.getString(R.string.cannot_access_account));
return;
}
GtasksInvoker gtasksInvoker = this.gtasksInvoker.forAccount(account.getAccount());
pushLocalChanges(account, gtasksInvoker);
List<TaskList> gtaskLists = new ArrayList<>();
String nextPageToken = null;
String eTag = null;
do {
TaskLists remoteLists = gtasksInvoker.allGtaskLists(nextPageToken);
if (remoteLists == null) {
break;
}
eTag = remoteLists.getEtag();
List<TaskList> items = remoteLists.getItems();
if (items != null) {
gtaskLists.addAll(items);
}
nextPageToken = remoteLists.getNextPageToken();
} while (!isNullOrEmpty(nextPageToken));
gtasksListService.updateLists(account, gtaskLists);
Filter defaultRemoteList = defaultFilterProvider.getDefaultList();
if (defaultRemoteList instanceof GtasksFilter) {
GoogleTaskList list =
googleTaskListDao.getByRemoteId(((GtasksFilter) defaultRemoteList).getRemoteId());
if (list == null) {
preferences.setString(R.string.p_default_list, null);
}
}
for (GoogleTaskList list :
googleTaskListDao.getByRemoteId(transform(gtaskLists, TaskList::getId))) {
if (isNullOrEmpty(list.getRemoteId())) {
firebase.reportException(new RuntimeException("Empty remote id"));
continue;
}
fetchAndApplyRemoteChanges(gtasksInvoker, list);
if (!preferences.isPositionHackEnabled()) {
googleTaskDao.reposition(list.getRemoteId());
}
}
if (preferences.isPositionHackEnabled()) {
for (TaskList list : gtaskLists) {
List<com.google.api.services.tasks.model.Task> tasks =
fetchPositions(gtasksInvoker, list.getId());
for (com.google.api.services.tasks.model.Task task : tasks) {
googleTaskDao.updatePosition(task.getId(), task.getParent(), task.getPosition());
}
googleTaskDao.reposition(list.getId());
}
}
account.setEtag(eTag);
account.setError("");
}
private List<com.google.api.services.tasks.model.Task> fetchPositions(
GtasksInvoker gtasksInvoker, String listId) throws IOException {
List<com.google.api.services.tasks.model.Task> tasks = new ArrayList<>();
String nextPageToken = null;
do {
Tasks taskList = gtasksInvoker.getAllPositions(listId, nextPageToken);
if (taskList == null) {
break;
}
List<com.google.api.services.tasks.model.Task> items = taskList.getItems();
if (items != null) {
tasks.addAll(items);
}
nextPageToken = taskList.getNextPageToken();
} while (!isNullOrEmpty(nextPageToken));
return tasks;
}
private void pushLocalChanges(GoogleTaskAccount account, GtasksInvoker gtasksInvoker)
throws IOException {
List<Task> tasks = taskDao.getGoogleTasksToPush(account.getAccount());
for (Task task : tasks) {
pushTask(task, gtasksInvoker);
}
}
private void pushTask(Task task, GtasksInvoker gtasksInvoker) throws IOException {
for (GoogleTask deleted : googleTaskDao.getDeletedByTaskId(task.getId())) {
gtasksInvoker.deleteGtask(deleted.getListId(), deleted.getRemoteId());
googleTaskDao.delete(deleted);
}
GoogleTask gtasksMetadata = googleTaskDao.getByTaskId(task.getId());
if (gtasksMetadata == null) {
return;
}
com.google.api.services.tasks.model.Task remoteModel =
new com.google.api.services.tasks.model.Task();
boolean newlyCreated = false;
String remoteId;
Filter defaultRemoteList = defaultFilterProvider.getDefaultList();
String listId =
defaultRemoteList instanceof GtasksFilter
? ((GtasksFilter) defaultRemoteList).getRemoteId()
: DEFAULT_LIST;
if (isNullOrEmpty(gtasksMetadata.getRemoteId())) { // Create case
String selectedList = gtasksMetadata.getListId();
if (!isNullOrEmpty(selectedList)) {
listId = selectedList;
}
newlyCreated = true;
} else { // update case
remoteId = gtasksMetadata.getRemoteId();
listId = gtasksMetadata.getListId();
remoteModel.setId(remoteId);
}
// If task was newly created but without a title, don't sync--we're in the middle of
// creating a task which may end up being cancelled. Also don't sync new but already
// deleted tasks
if (newlyCreated && (isNullOrEmpty(task.getTitle()) || task.getDeletionDate() > 0)) {
return;
}
// Update the remote model's changed properties
if (task.isDeleted()) {
remoteModel.setDeleted(true);
}
remoteModel.setTitle(truncate(task.getTitle(), MAX_TITLE_LENGTH));
remoteModel.setNotes(truncate(task.getNotes(), MAX_DESCRIPTION_LENGTH));
if (task.hasDueDate()) {
remoteModel.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(task.getDueDate()));
}
if (task.isCompleted()) {
remoteModel.setCompleted(
GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.getCompletionDate()));
remoteModel.setStatus("completed"); // $NON-NLS-1$
} else {
remoteModel.setCompleted(null);
remoteModel.setStatus("needsAction"); // $NON-NLS-1$
}
if (newlyCreated) {
long parent = gtasksMetadata.getParent();
String localParent = parent > 0 ? googleTaskDao.getRemoteId(parent) : null;
String previous =
googleTaskDao.getPrevious(
listId, isNullOrEmpty(localParent) ? 0 : parent, gtasksMetadata.getOrder());
com.google.api.services.tasks.model.Task created;
try {
created = gtasksInvoker.createGtask(listId, remoteModel, localParent, previous);
} catch (HttpNotFoundException e) {
created = gtasksInvoker.createGtask(listId, remoteModel, null, null);
}
if (created != null) {
// Update the metadata for the newly created task
gtasksMetadata.setRemoteId(created.getId());
gtasksMetadata.setListId(listId);
gtasksMetadata.setRemoteOrder(Long.parseLong(created.getPosition()));
gtasksMetadata.setRemoteParent(created.getParent());
} else {
return;
}
} else {
try {
if (!task.isDeleted() && gtasksMetadata.isMoved()) {
try {
long parent = gtasksMetadata.getParent();
String localParent = parent > 0 ? googleTaskDao.getRemoteId(parent) : null;
String previous =
googleTaskDao.getPrevious(
listId,
isNullOrEmpty(localParent) ? 0 : parent,
gtasksMetadata.getOrder());
com.google.api.services.tasks.model.Task result =
gtasksInvoker.moveGtask(listId, remoteModel.getId(), localParent, previous);
gtasksMetadata.setRemoteOrder(Long.parseLong(result.getPosition()));
gtasksMetadata.setRemoteParent(result.getParent());
gtasksMetadata.setParent(
isNullOrEmpty(result.getParent())
? 0
: googleTaskDao.getTask(result.getParent()));
} catch (GoogleJsonResponseException e) {
if (e.getStatusCode() == 400) {
Timber.e(e);
} else {
throw e;
}
}
}
// TODO: don't updateGtask if it was only moved
gtasksInvoker.updateGtask(listId, remoteModel);
} catch (HttpNotFoundException e) {
googleTaskDao.delete(gtasksMetadata);
return;
}
}
task.setModificationDate(DateUtilities.now());
gtasksMetadata.setMoved(false);
gtasksMetadata.setLastSync(DateUtilities.now() + 1000L);
if (gtasksMetadata.getId() == Task.NO_ID) {
googleTaskDao.insert(gtasksMetadata);
} else {
googleTaskDao.update(gtasksMetadata);
}
task.suppressSync();
taskDao.save(task);
}
private synchronized void fetchAndApplyRemoteChanges(
GtasksInvoker gtasksInvoker, GoogleTaskList list) throws IOException {
String listId = list.getRemoteId();
long lastSyncDate = list.getLastSync();
List<com.google.api.services.tasks.model.Task> tasks = new ArrayList<>();
String nextPageToken = null;
do {
Tasks taskList;
try {
taskList =
gtasksInvoker.getAllGtasksFromListId(listId, lastSyncDate + 1000L, nextPageToken);
} catch (HttpNotFoundException e) {
firebase.reportException(e);
return;
}
if (taskList == null) {
break;
}
List<com.google.api.services.tasks.model.Task> items = taskList.getItems();
if (items != null) {
tasks.addAll(items);
}
nextPageToken = taskList.getNextPageToken();
} while (!isNullOrEmpty(nextPageToken));
Collections.sort(tasks, PARENTS_FIRST);
for (com.google.api.services.tasks.model.Task gtask : tasks) {
String remoteId = gtask.getId();
GoogleTask googleTask = googleTaskDao.getByRemoteId(remoteId);
Task task = null;
if (googleTask == null) {
googleTask = new GoogleTask(0, "");
} else if (googleTask.getTask() > 0) {
task = taskDao.fetchBlocking(googleTask.getTask());
}
com.google.api.client.util.DateTime updated = gtask.getUpdated();
if (updated != null) {
lastSyncDate = Math.max(lastSyncDate, updated.getValue());
}
Boolean isDeleted = gtask.getDeleted();
Boolean isHidden = gtask.getHidden();
if (isDeleted != null && isDeleted) {
if (task != null) {
taskDeleter.delete(task);
}
continue;
} else if (isHidden != null && isHidden) {
if (task == null) {
continue;
}
if (task.isRecurring()) {
googleTask.setRemoteId("");
} else {
taskDeleter.delete(task);
continue;
}
} else {
googleTask.setRemoteOrder(Long.parseLong(gtask.getPosition()));
googleTask.setRemoteParent(gtask.getParent());
googleTask.setParent(
isNullOrEmpty(gtask.getParent())
? 0
: googleTaskDao.getTask(gtask.getParent()));
googleTask.setRemoteId(gtask.getId());
}
if (task == null) {
task = taskCreator.createWithValues("");
}
task.setTitle(getTruncatedValue(task.getTitle(), gtask.getTitle(), MAX_TITLE_LENGTH));
task.setCreationDate(DateUtilities.now());
task.setCompletionDate(
GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtask.getCompleted()));
long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(gtask.getDue());
mergeDates(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate), task);
task.setNotes(getTruncatedValue(task.getNotes(), gtask.getNotes(), MAX_DESCRIPTION_LENGTH));
googleTask.setListId(listId);
googleTask.setLastSync(DateUtilities.now() + 1000L);
write(task, googleTask);
}
list.setLastSync(lastSyncDate);
googleTaskListDao.insertOrReplace(list);
}
static String truncate(@Nullable String string, int max) {
return string == null || string.length() <= max ? string : string.substring(0, max);
}
static String getTruncatedValue(@Nullable String currentValue, @Nullable String newValue, int maxLength) {
return isNullOrEmpty(newValue)
|| newValue.length() < maxLength
|| isNullOrEmpty(currentValue)
|| !currentValue.startsWith(newValue)
? newValue
: currentValue;
}
private void write(Task task, GoogleTask googleTask) {
if (!(isNullOrEmpty(task.getTitle()) && isNullOrEmpty(task.getNotes()))) {
task.suppressSync();
task.suppressRefresh();
if (task.isNew()) {
taskDao.createNew(task);
}
taskDao.save(task);
googleTask.setTask(task.getId());
if (googleTask.getId() == 0) {
googleTaskDao.insert(googleTask);
} else {
googleTaskDao.update(googleTask);
}
}
}
}

@ -0,0 +1,414 @@
package org.tasks.gtasks
import android.content.Context
import com.google.api.client.googleapis.json.GoogleJsonResponseException
import com.google.api.services.tasks.model.Task
import com.google.api.services.tasks.model.TaskList
import com.google.api.services.tasks.model.Tasks
import com.google.common.collect.Lists
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDaoBlocking
import com.todoroo.astrid.data.Task.Companion.createDueDate
import com.todoroo.astrid.gtasks.GtasksListService
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities
import com.todoroo.astrid.gtasks.api.GtasksInvoker
import com.todoroo.astrid.gtasks.api.HttpNotFoundException
import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory
import org.tasks.data.*
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences
import timber.log.Timber
import java.io.EOFException
import java.io.IOException
import java.net.HttpRetryException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.*
import javax.inject.Inject
import javax.net.ssl.SSLException
import kotlin.math.max
class GoogleTaskSynchronizer @Inject constructor(
@param:ApplicationContext private val context: Context,
private val googleTaskListDao: GoogleTaskListDaoBlocking,
private val gtasksListService: GtasksListService,
private val preferences: Preferences,
private val taskDao: TaskDaoBlocking,
private val firebase: Firebase,
private val googleTaskDao: GoogleTaskDaoBlocking,
private val taskCreator: TaskCreator,
private val defaultFilterProvider: DefaultFilterProvider,
private val permissionChecker: PermissionChecker,
private val googleAccountManager: GoogleAccountManager,
private val localBroadcastManager: LocalBroadcastManager,
private val inventory: Inventory,
private val taskDeleter: TaskDeleter,
private val gtasksInvoker: GtasksInvoker) {
fun sync(account: GoogleTaskAccount, i: Int) {
Timber.d("%s: start sync", account)
try {
if (i == 0 || inventory.hasPro()) {
synchronize(account)
} else {
account.error = context.getString(R.string.requires_pro_subscription)
}
} catch (e: SocketTimeoutException) {
Timber.e(e)
account.error = e.message
} catch (e: SSLException) {
Timber.e(e)
account.error = e.message
} catch (e: SocketException) {
Timber.e(e)
account.error = e.message
} catch (e: UnknownHostException) {
Timber.e(e)
account.error = e.message
} catch (e: HttpRetryException) {
Timber.e(e)
account.error = e.message
} catch (e: EOFException) {
Timber.e(e)
account.error = e.message
} catch (e: GoogleJsonResponseException) {
account.error = e.message
if (e.statusCode == 401) {
Timber.e(e)
} else {
firebase.reportException(e)
}
} catch (e: Exception) {
account.error = e.message
firebase.reportException(e)
} finally {
googleTaskListDao.update(account)
localBroadcastManager.broadcastRefreshList()
Timber.d("%s: end sync", account)
}
}
@Throws(IOException::class)
private fun synchronize(account: GoogleTaskAccount) {
if (!permissionChecker.canAccessAccounts()
|| googleAccountManager.getAccount(account.account) == null) {
account.error = context.getString(R.string.cannot_access_account)
return
}
val gtasksInvoker = gtasksInvoker.forAccount(account.account)
pushLocalChanges(account, gtasksInvoker)
val gtaskLists: MutableList<TaskList> = ArrayList()
var nextPageToken: String? = null
var eTag: String? = null
do {
val remoteLists = gtasksInvoker.allGtaskLists(nextPageToken) ?: break
eTag = remoteLists.etag
val items = remoteLists.items
if (items != null) {
gtaskLists.addAll(items)
}
nextPageToken = remoteLists.nextPageToken
} while (!isNullOrEmpty(nextPageToken))
gtasksListService.updateLists(account, gtaskLists)
val defaultRemoteList = defaultFilterProvider.defaultList
if (defaultRemoteList is GtasksFilter) {
val list = googleTaskListDao.getByRemoteId(defaultRemoteList.remoteId)
if (list == null) {
preferences.setString(R.string.p_default_list, null)
}
}
for (list in googleTaskListDao.getByRemoteId(Lists.transform(gtaskLists) { obj: TaskList? -> obj!!.id })) {
if (isNullOrEmpty(list.remoteId)) {
firebase.reportException(RuntimeException("Empty remote id"))
continue
}
fetchAndApplyRemoteChanges(gtasksInvoker, list)
if (!preferences.isPositionHackEnabled) {
googleTaskDao.reposition(list.remoteId!!)
}
}
if (preferences.isPositionHackEnabled) {
for (list in gtaskLists) {
val tasks = fetchPositions(gtasksInvoker, list.id)
for (task in tasks) {
googleTaskDao.updatePosition(task.id, task.parent, task.position)
}
googleTaskDao.reposition(list.id)
}
}
account.etag = eTag
account.error = ""
}
@Throws(IOException::class)
private fun fetchPositions(
gtasksInvoker: GtasksInvoker, listId: String): List<Task> {
val tasks: MutableList<Task> = ArrayList()
var nextPageToken: String? = null
do {
val taskList = gtasksInvoker.getAllPositions(listId, nextPageToken) ?: break
val items = taskList.items
if (items != null) {
tasks.addAll(items)
}
nextPageToken = taskList.nextPageToken
} while (!isNullOrEmpty(nextPageToken))
return tasks
}
@Throws(IOException::class)
private fun pushLocalChanges(account: GoogleTaskAccount, gtasksInvoker: GtasksInvoker) {
val tasks = taskDao.getGoogleTasksToPush(account.account!!)
for (task in tasks) {
pushTask(task, gtasksInvoker)
}
}
@Throws(IOException::class)
private fun pushTask(task: com.todoroo.astrid.data.Task, gtasksInvoker: GtasksInvoker) {
for (deleted in googleTaskDao.getDeletedByTaskId(task.id)) {
gtasksInvoker.deleteGtask(deleted.listId, deleted.remoteId)
googleTaskDao.delete(deleted)
}
val gtasksMetadata = googleTaskDao.getByTaskId(task.id) ?: return
val remoteModel = Task()
var newlyCreated = false
val remoteId: String?
val defaultRemoteList = defaultFilterProvider.defaultList
var listId = if (defaultRemoteList is GtasksFilter) defaultRemoteList.remoteId else DEFAULT_LIST
if (isNullOrEmpty(gtasksMetadata.remoteId)) { // Create case
val selectedList = gtasksMetadata.listId
if (!isNullOrEmpty(selectedList)) {
listId = selectedList
}
newlyCreated = true
} else { // update case
remoteId = gtasksMetadata.remoteId
listId = gtasksMetadata.listId
remoteModel.id = remoteId
}
// If task was newly created but without a title, don't sync--we're in the middle of
// creating a task which may end up being cancelled. Also don't sync new but already
// deleted tasks
if (newlyCreated && (isNullOrEmpty(task.title) || task.deletionDate > 0)) {
return
}
// Update the remote model's changed properties
if (task.isDeleted) {
remoteModel.deleted = true
}
remoteModel.title = truncate(task.title, MAX_TITLE_LENGTH)
remoteModel.notes = truncate(task.notes, MAX_DESCRIPTION_LENGTH)
if (task.hasDueDate()) {
remoteModel.due = GtasksApiUtilities.unixTimeToGtasksDueDate(task.dueDate)
}
if (task.isCompleted) {
remoteModel.completed = GtasksApiUtilities.unixTimeToGtasksCompletionTime(task.completionDate)
remoteModel.status = "completed" // $NON-NLS-1$
} else {
remoteModel.completed = null
remoteModel.status = "needsAction" // $NON-NLS-1$
}
if (newlyCreated) {
val parent = gtasksMetadata.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious(
listId!!, if (isNullOrEmpty(localParent)) 0 else parent, gtasksMetadata.order)
val created: Task?
created = try {
gtasksInvoker.createGtask(listId, remoteModel, localParent, previous)
} catch (e: HttpNotFoundException) {
gtasksInvoker.createGtask(listId, remoteModel, null, null)
}
if (created != null) {
// Update the metadata for the newly created task
gtasksMetadata.remoteId = created.id
gtasksMetadata.listId = listId
gtasksMetadata.remoteOrder = created.position.toLong()
gtasksMetadata.remoteParent = created.parent
} else {
return
}
} else {
try {
if (!task.isDeleted && gtasksMetadata.isMoved) {
try {
val parent = gtasksMetadata.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious(
listId!!,
if (isNullOrEmpty(localParent)) 0 else parent,
gtasksMetadata.order)
val result = gtasksInvoker.moveGtask(listId, remoteModel.id, localParent, previous)
gtasksMetadata.remoteOrder = result!!.position.toLong()
gtasksMetadata.remoteParent = result.parent
gtasksMetadata.parent = if (isNullOrEmpty(result.parent)) 0 else googleTaskDao.getTask(result.parent)
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 400) {
Timber.e(e)
} else {
throw e
}
}
}
// TODO: don't updateGtask if it was only moved
gtasksInvoker.updateGtask(listId, remoteModel)
} catch (e: HttpNotFoundException) {
googleTaskDao.delete(gtasksMetadata)
return
}
}
task.modificationDate = DateUtilities.now()
gtasksMetadata.isMoved = false
gtasksMetadata.lastSync = DateUtilities.now() + 1000L
if (gtasksMetadata.id == com.todoroo.astrid.data.Task.NO_ID) {
googleTaskDao.insert(gtasksMetadata)
} else {
googleTaskDao.update(gtasksMetadata)
}
task.suppressSync()
taskDao.save(task)
}
@Synchronized
@Throws(IOException::class)
private fun fetchAndApplyRemoteChanges(
gtasksInvoker: GtasksInvoker, list: GoogleTaskList) {
val listId = list.remoteId
var lastSyncDate = list.lastSync
val tasks: MutableList<Task> = ArrayList()
var nextPageToken: String? = null
do {
val taskList: Tasks = try {
gtasksInvoker.getAllGtasksFromListId(listId, lastSyncDate + 1000L, nextPageToken)
} catch (e: HttpNotFoundException) {
firebase.reportException(e)
return
} ?: break
val items = taskList.items
if (items != null) {
tasks.addAll(items)
}
nextPageToken = taskList.nextPageToken
} while (!isNullOrEmpty(nextPageToken))
Collections.sort(tasks, PARENTS_FIRST)
for (gtask in tasks) {
val remoteId = gtask.id
var googleTask = googleTaskDao.getByRemoteId(remoteId)
var task: com.todoroo.astrid.data.Task? = null
if (googleTask == null) {
googleTask = GoogleTask(0, "")
} else if (googleTask.task > 0) {
task = taskDao.fetchBlocking(googleTask.task)
}
val updated = gtask.updated
if (updated != null) {
lastSyncDate = max(lastSyncDate, updated.value)
}
val isDeleted = gtask.deleted
val isHidden = gtask.hidden
if (isDeleted != null && isDeleted) {
if (task != null) {
taskDeleter.delete(task)
}
continue
} else if (isHidden != null && isHidden) {
if (task == null) {
continue
}
if (task.isRecurring) {
googleTask.remoteId = ""
} else {
taskDeleter.delete(task)
continue
}
} else {
googleTask.remoteOrder = gtask.position.toLong()
googleTask.remoteParent = gtask.parent
googleTask.parent = if (isNullOrEmpty(gtask.parent)) 0 else googleTaskDao.getTask(gtask.parent)
googleTask.remoteId = gtask.id
}
if (task == null) {
task = taskCreator.createWithValues("")
}
task!!.title = getTruncatedValue(task.title, gtask.title, MAX_TITLE_LENGTH)
task.creationDate = DateUtilities.now()
task.completionDate = GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtask.completed)
val dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(gtask.due)
mergeDates(createDueDate(com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY, dueDate), task)
task.notes = getTruncatedValue(task.notes, gtask.notes, MAX_DESCRIPTION_LENGTH)
googleTask.listId = listId
googleTask.lastSync = DateUtilities.now() + 1000L
write(task, googleTask)
}
list.lastSync = lastSyncDate
googleTaskListDao.insertOrReplace(list)
}
private fun write(task: com.todoroo.astrid.data.Task?, googleTask: GoogleTask) {
if (!(isNullOrEmpty(task!!.title) && isNullOrEmpty(task.notes))) {
task.suppressSync()
task.suppressRefresh()
if (task.isNew) {
taskDao.createNew(task)
}
taskDao.save(task)
googleTask.task = task.id
if (googleTask.id == 0L) {
googleTaskDao.insert(googleTask)
} else {
googleTaskDao.update(googleTask)
}
}
}
companion object {
private const val DEFAULT_LIST = "@default" // $NON-NLS-1$
private const val MAX_TITLE_LENGTH = 1024
private const val MAX_DESCRIPTION_LENGTH = 8192
private val PARENTS_FIRST = Comparator { o1: Task, o2: Task ->
if (isNullOrEmpty(o1.parent)) {
if (isNullOrEmpty(o2.parent)) 0 else -1
} else {
if (isNullOrEmpty(o2.parent)) 1 else 0
}
}
fun mergeDates(remoteDueDate: Long, local: com.todoroo.astrid.data.Task?) {
if (remoteDueDate > 0 && local!!.hasDueTime()) {
val oldDate = newDateTime(local.dueDate)
val newDate = newDateTime(remoteDueDate)
.withHourOfDay(oldDate.hourOfDay)
.withMinuteOfHour(oldDate.minuteOfHour)
.withSecondOfMinute(oldDate.secondOfMinute)
local.setDueDateAdjustingHideUntil(
createDueDate(com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY_TIME, newDate.millis))
} else {
local!!.setDueDateAdjustingHideUntil(remoteDueDate)
}
}
fun truncate(string: String?, max: Int): String? {
return if (string == null || string.length <= max) string else string.substring(0, max)
}
fun getTruncatedValue(currentValue: String?, newValue: String?, maxLength: Int): String? {
return if (isNullOrEmpty(newValue)
|| newValue!!.length < maxLength || isNullOrEmpty(currentValue)
|| !currentValue!!.startsWith(newValue)) newValue else currentValue
}
}
}
Loading…
Cancel
Save