From 56f3dceec7fdeb558aac15292be2b22bf72af115 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 8 Jul 2019 12:59:57 -0500 Subject: [PATCH] Fix Google Task and Google Drive issues --- CHANGELOG.md | 6 + app/build.gradle.kts | 4 +- .../org/tasks/caldav/CaldavSynchronizer.java | 174 +++++++++--------- .../tasks/gtasks/GoogleTaskSynchronizer.java | 24 ++- .../java/org/tasks/jobs/DriveUploader.java | 18 +- 5 files changed, 135 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0b01fb0..830ab4acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ Change Log --- +### 6.7.2 (2019-07-08) + +* Handle 404 errors when creating new Google Tasks +* Ignore 404 errors when deleting Google Drive files +* Don't report connection errors + ### 6.7.1 (2019-07-05) * Add location chip to task list diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0077a01a1..6ca766c24 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,8 +25,8 @@ android { defaultConfig { testApplicationId = "org.tasks.test" applicationId = "org.tasks" - versionCode = 591 - versionName = "6.7.1" + versionCode = 592 + versionName = "6.7.2" targetSdkVersion(Versions.compileSdk) minSdkVersion(Versions.minSdk) multiDexEnabled = true diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.java b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.java index 832cb88d4..445b2f370 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.java +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.java @@ -34,10 +34,14 @@ import com.todoroo.astrid.service.TaskDeleter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.inject.Inject; +import javax.net.ssl.SSLException; import net.fortuna.ical4j.model.property.ProdId; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -102,15 +106,20 @@ public class CaldavSynchronizer { setError(account, context.getString(R.string.password_required)); return; } - CaldavClient caldavClient = client.forAccount(account); - List resources; try { - resources = caldavClient.getCalendars(); + synchronize(account); + } catch (SocketTimeoutException | SSLException | ConnectException | UnknownHostException e) { + Timber.e(e); + account.setError(e.getMessage()); } catch (IOException | DavException e) { setError(account, e.getMessage()); tracker.reportException(e); - return; } + } + + private void synchronize(CaldavAccount account) throws IOException, DavException { + CaldavClient caldavClient = client.forAccount(account); + List resources = caldavClient.getCalendars(); Set urls = newHashSet(transform(resources, c -> c.getHref().toString())); Timber.d("Found calendars: %s", urls); for (CaldavCalendar calendar : @@ -143,103 +152,98 @@ public class CaldavSynchronizer { } } - private void sync(CaldavCalendar caldavCalendar, Response resource, OkHttpClient httpClient) { + private void sync(CaldavCalendar caldavCalendar, Response resource, OkHttpClient httpClient) + throws IOException, DavException { Timber.d("sync(%s)", caldavCalendar); HttpUrl httpUrl = resource.getHref(); - try { - pushLocalChanges(caldavCalendar, httpClient, httpUrl); - - String remoteName = resource.get(DisplayName.class).getDisplayName(); - if (!caldavCalendar.getName().equals(remoteName)) { - Timber.d("%s -> %s", caldavCalendar.getName(), remoteName); - caldavCalendar.setName(remoteName); - caldavDao.update(caldavCalendar); - localBroadcastManager.broadcastRefreshList(); - } + pushLocalChanges(caldavCalendar, httpClient, httpUrl); + + String remoteName = resource.get(DisplayName.class).getDisplayName(); + if (!caldavCalendar.getName().equals(remoteName)) { + Timber.d("%s -> %s", caldavCalendar.getName(), remoteName); + caldavCalendar.setName(remoteName); + caldavDao.update(caldavCalendar); + localBroadcastManager.broadcastRefreshList(); + } - String remoteCtag = resource.get(GetCTag.class).getCTag(); - String localCtag = caldavCalendar.getCtag(); + String remoteCtag = resource.get(GetCTag.class).getCTag(); + String localCtag = caldavCalendar.getCtag(); - if (localCtag != null && localCtag.equals(remoteCtag)) { - Timber.d("%s up to date", caldavCalendar.getName()); - return; - } + if (localCtag != null && localCtag.equals(remoteCtag)) { + Timber.d("%s up to date", caldavCalendar.getName()); + return; + } + + DavCalendar davCalendar = new DavCalendar(httpClient, httpUrl); + + ResponseList members = new ResponseList(HrefRelation.MEMBER); + davCalendar.calendarQuery("VTODO", null, null, members); + + Set remoteObjects = newHashSet(transform(members, Response::hrefName)); + + Iterable changed = + filter( + ImmutableSet.copyOf(members), + vCard -> { + GetETag eTag = vCard.get(GetETag.class); + if (eTag == null || isNullOrEmpty(eTag.getETag())) { + return false; + } + CaldavTask caldavTask = caldavDao.getTask(caldavCalendar.getUuid(), vCard.hrefName()); + return caldavTask == null || !eTag.getETag().equals(caldavTask.getEtag()); + }); + + for (List items : partition(changed, 30)) { + if (items.size() == 1) { + Response vCard = items.get(0); + GetETag eTag = vCard.get(GetETag.class); + HttpUrl url = vCard.getHref(); + if (eTag == null || isNullOrEmpty(eTag.getETag())) { + throw new DavException("Received CalDAV GET response without ETag for " + url); + } + Timber.d("SINGLE %s", url); - DavCalendar davCalendar = new DavCalendar(httpClient, httpUrl); - - ResponseList members = new ResponseList(HrefRelation.MEMBER); - davCalendar.calendarQuery("VTODO", null, null, members); - - Set remoteObjects = newHashSet(transform(members, Response::hrefName)); - - Iterable changed = - filter( - ImmutableSet.copyOf(members), - vCard -> { - GetETag eTag = vCard.get(GetETag.class); - if (eTag == null || isNullOrEmpty(eTag.getETag())) { - return false; - } - CaldavTask caldavTask = - caldavDao.getTask(caldavCalendar.getUuid(), vCard.hrefName()); - return caldavTask == null || !eTag.getETag().equals(caldavTask.getEtag()); - }); - - for (List items : partition(changed, 30)) { - if (items.size() == 1) { - Response vCard = items.get(0); + org.tasks.caldav.Response response = new org.tasks.caldav.Response(true); + new DavResource(httpClient, url).get("text/calendar", response); + processVTodo(vCard.hrefName(), caldavCalendar, eTag.getETag(), response.getBody()); + } else { + ArrayList urls = newArrayList(Iterables.transform(items, Response::getHref)); + ResponseList responses = new ResponseList(HrefRelation.MEMBER); + davCalendar.multiget(urls, responses); + + Timber.d("MULTI %s", urls); + + for (Response vCard : responses) { GetETag eTag = vCard.get(GetETag.class); HttpUrl url = vCard.getHref(); if (eTag == null || isNullOrEmpty(eTag.getETag())) { throw new DavException("Received CalDAV GET response without ETag for " + url); } - Timber.d("SINGLE %s", url); - - org.tasks.caldav.Response response = new org.tasks.caldav.Response(true); - new DavResource(httpClient, url).get("text/calendar", response); - processVTodo(vCard.hrefName(), caldavCalendar, eTag.getETag(), response.getBody()); - } else { - ArrayList urls = newArrayList(Iterables.transform(items, Response::getHref)); - ResponseList responses = new ResponseList(HrefRelation.MEMBER); - davCalendar.multiget(urls, responses); - - Timber.d("MULTI %s", urls); - - for (Response vCard : responses) { - GetETag eTag = vCard.get(GetETag.class); - HttpUrl url = vCard.getHref(); - if (eTag == null || isNullOrEmpty(eTag.getETag())) { - throw new DavException("Received CalDAV GET response without ETag for " + url); - } - CalendarData calendarData = vCard.get(CalendarData.class); - if (calendarData == null || isNullOrEmpty(calendarData.getICalendar())) { - throw new DavException( - "Received CalDAV GET response without CalendarData for " + url); - } - - processVTodo( - vCard.hrefName(), caldavCalendar, eTag.getETag(), calendarData.getICalendar()); + CalendarData calendarData = vCard.get(CalendarData.class); + if (calendarData == null || isNullOrEmpty(calendarData.getICalendar())) { + throw new DavException("Received CalDAV GET response without CalendarData for " + url); } - } - } - List deleted = - newArrayList( - difference( - newHashSet(caldavDao.getObjects(caldavCalendar.getUuid())), - newHashSet(remoteObjects))); - if (deleted.size() > 0) { - Timber.d("DELETED %s", deleted); - taskDeleter.delete(caldavDao.getTasks(caldavCalendar.getUuid(), deleted)); + processVTodo( + vCard.hrefName(), caldavCalendar, eTag.getETag(), calendarData.getICalendar()); + } } + } - caldavCalendar.setCtag(remoteCtag); - Timber.d("UPDATE %s", caldavCalendar); - caldavDao.update(caldavCalendar); - } catch (Exception e) { - tracker.reportException(e); + List deleted = + newArrayList( + difference( + newHashSet(caldavDao.getObjects(caldavCalendar.getUuid())), + newHashSet(remoteObjects))); + if (deleted.size() > 0) { + Timber.d("DELETED %s", deleted); + taskDeleter.delete(caldavDao.getTasks(caldavCalendar.getUuid(), deleted)); } + caldavCalendar.setCtag(remoteCtag); + Timber.d("UPDATE %s", caldavCalendar); + caldavDao.update(caldavCalendar); + localBroadcastManager.broadcastRefresh(); } diff --git a/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java b/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java index fd62ddc7a..ce6adfbf9 100644 --- a/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java +++ b/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java @@ -28,11 +28,15 @@ import com.todoroo.astrid.service.TaskCreator; import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.utility.Constants; import java.io.IOException; +import java.net.ConnectException; +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.Tracker; @@ -139,6 +143,9 @@ public class GoogleTaskSynchronizer { } else { account.setError(context.getString(R.string.requires_pro_subscription)); } + } catch (SocketTimeoutException | SSLException | ConnectException | UnknownHostException e) { + Timber.e(e); + account.setError(e.getMessage()); } catch (UserRecoverableAuthIOException e) { Timber.e(e); sendNotification(context, e.getIntent()); @@ -322,10 +329,21 @@ public class GoogleTaskSynchronizer { : null; String previous = - googleTaskDao.getPrevious(listId, gtasksMetadata.getParent(), gtasksMetadata.getOrder()); + Strings.isNullOrEmpty(localParent) + ? null + : googleTaskDao.getPrevious( + listId, gtasksMetadata.getParent(), gtasksMetadata.getOrder()); - com.google.api.services.tasks.model.Task created = - gtasksInvoker.createGtask(listId, remoteModel, localParent, previous); + com.google.api.services.tasks.model.Task created; + try { + created = gtasksInvoker.createGtask(listId, remoteModel, localParent, previous); + } catch (GoogleJsonResponseException e) { + if (e.getStatusCode() == 404) { + created = gtasksInvoker.createGtask(listId, remoteModel, null, null); + } else { + throw e; + } + } if (created != null) { // Update the metadata for the newly created task diff --git a/app/src/main/java/org/tasks/jobs/DriveUploader.java b/app/src/main/java/org/tasks/jobs/DriveUploader.java index 608b5785b..7fd564a0f 100644 --- a/app/src/main/java/org/tasks/jobs/DriveUploader.java +++ b/app/src/main/java/org/tasks/jobs/DriveUploader.java @@ -12,10 +12,14 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.services.drive.model.File; import com.google.common.base.Strings; import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.inject.Inject; +import javax.net.ssl.SSLException; import org.tasks.R; import org.tasks.analytics.Tracker; import org.tasks.drive.DriveInvoker; @@ -23,6 +27,7 @@ import org.tasks.injection.ForApplication; import org.tasks.injection.InjectingWorker; import org.tasks.injection.JobComponent; import org.tasks.preferences.Preferences; +import timber.log.Timber; public class DriveUploader extends InjectingWorker { @@ -65,11 +70,22 @@ public class DriveUploader extends InjectingWorker { if (inputData.getBoolean(EXTRA_PURGE, false)) { List files = drive.getFilesByPrefix(folder.getId(), "auto."); for (File file : getDeleteList(files)) { - drive.delete(file); + try { + drive.delete(file); + } catch (GoogleJsonResponseException e) { + if (e.getStatusCode() == 404) { + Timber.e(e); + } else { + throw e; + } + } } } return Result.success(); + } catch (SocketTimeoutException | SSLException | ConnectException | UnknownHostException e) { + Timber.e(e); + return Result.retry(); } catch (IOException e) { tracker.reportException(e); return Result.failure();