From f6eee6c910fb7255d2114789a0f9698894af8a3b Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 28 Apr 2015 11:08:22 +0200 Subject: [PATCH] Handle dirty flag of exceptions of recurring events * mark events as dirty when its exceptions are dirty/deleted * when (mass-)deleting events, delete corresponding exceptions too --- .../davdroid/resource/LocalCalendar.java | 80 +++++++++++++++---- .../davdroid/resource/LocalCollection.java | 9 ++- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java index e7d8ec4d..b486e842 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java @@ -206,6 +206,24 @@ public class LocalCalendar extends LocalCollection { } } + @Override + public long[] findUpdated() throws LocalStorageException { + // mark (recurring) events with changed/deleted exceptions as dirty + String where = entryColumnID() + " IN (SELECT DISTINCT " + Events.ORIGINAL_ID + " FROM events WHERE " + + Events.ORIGINAL_ID + " IS NOT NULL AND (" + Events.DIRTY + "=1 OR " + Events.DELETED + "=1))"; + Log.i(TAG, where); + ContentValues dirty = new ContentValues(1); + dirty.put(CalendarContract.Events.DIRTY, 1); + try { + providerClient.update(entriesURI(), dirty, where, null); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't mark events with updated exceptions as dirty", e); + } + + // new find and return updated (master) events + return super.findUpdated(); + } + /* create/update/delete */ @@ -214,23 +232,57 @@ public class LocalCalendar extends LocalCollection { } public void deleteAllExceptRemoteNames(Resource[] remoteResources) { + List sqlFileNames = new LinkedList<>(); + for (Resource res : remoteResources) + sqlFileNames.add(DatabaseUtils.sqlEscapeString(res.getName())); + + // delete master events String where = entryColumnParentID() + "=?"; - - if (remoteResources.length != 0) { - List sqlFileNames = new LinkedList(); - for (Resource res : remoteResources) - sqlFileNames.add(DatabaseUtils.sqlEscapeString(res.getName())); - where += " AND " + entryColumnRemoteName() + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ")"; - } else - where += " AND " + entryColumnRemoteName() + " IS NOT NULL"; + where += sqlFileNames.isEmpty() ? + " AND " + entryColumnRemoteName() + " IS NOT NULL" : // don't retain anything (delete all) + " AND " + entryColumnRemoteName() + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ")"; // retain by remote file name if (sqlFilter != null) where += " AND (" + sqlFilter + ")"; - - Builder builder = ContentProviderOperation.newDelete(entriesURI()) - .withSelection(where, new String[] { String.valueOf(id) }); - pendingOperations.add(builder - .withYieldAllowed(true) + pendingOperations.add(ContentProviderOperation.newDelete(entriesURI()) + .withSelection(where, new String[] { String.valueOf(id) }) .build()); + + // delete exceptions, too + where = entryColumnParentID() + "=?"; + where += sqlFileNames.isEmpty() ? + " AND " + Events.ORIGINAL_SYNC_ID + " IS NOT NULL" : // don't retain anything (delete all) + " AND " + Events.ORIGINAL_SYNC_ID + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ")"; // retain by remote file name + pendingOperations.add(ContentProviderOperation + .newDelete(entriesURI()) + .withSelection(where, new String[] { String.valueOf(id) }) + .withYieldAllowed(true) + .build() + ); + } + + @Override + public void delete(Resource resource) { + super.delete(resource); + + // delete all exceptions of this event, too + pendingOperations.add(ContentProviderOperation + .newDelete(entriesURI()) + .withSelection(Events.ORIGINAL_ID+"=?", new String[] { Long.toString(resource.getLocalID()) }) + .build() + ); + } + + @Override + public void clearDirty(Resource resource) { + super.clearDirty(resource); + + // clear dirty flag of all exceptions of this event + pendingOperations.add(ContentProviderOperation + .newUpdate(entriesURI()) + .withValue(Events.DIRTY, 0) + .withSelection(Events.ORIGINAL_ID+"=?", new String[] { Long.toString(resource.getLocalID()) }) + .build() + ); } @@ -478,7 +530,7 @@ public class LocalCalendar extends LocalCollection { e.getAlarms().add(alarm); } } - + /* content builder methods */ diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java index a3c0b4a1..a9d07564 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java @@ -92,6 +92,7 @@ public abstract class LocalCollection { /** * Finds new resources (resources which haven't been uploaded yet). * New resources are 1) dirty, and 2) don't have an ETag yet. + * Only records matching sqlFilter will be returned. * * @return IDs of new resources * @throws LocalStorageException when the content provider couldn't be queried @@ -132,7 +133,8 @@ public abstract class LocalCollection { /** * Finds updated resources (resources which have already been uploaded, but have changed locally). - * Updated resources are 1) dirty, and 2) already have an ETag. + * Updated resources are 1) dirty, and 2) already have an ETag. Only records matching sqlFilter + * will be returned. * * @return IDs of updated resources * @throws LocalStorageException when the content provider couldn't be queried @@ -162,6 +164,7 @@ public abstract class LocalCollection { /** * Finds deleted resources (resources which have been marked for deletion). * Deleted resources have the "deleted" flag set. + * Only records matching sqlFilter will be returned. * * @return IDs of deleted resources * @throws LocalStorageException when the content provider couldn't be queried @@ -189,7 +192,7 @@ public abstract class LocalCollection { } /** - * Finds a specific resource by ID. + * Finds a specific resource by ID. Only records matching sqlFilter are taken into account. * @param localID ID of the resource * @param populate true: populates all data fields (for instance, contact or event details); * false: only remote file name and ETag are populated @@ -214,7 +217,7 @@ public abstract class LocalCollection { } /** - * Finds a specific resource by remote file name. + * Finds a specific resource by remote file name. Only records matching sqlFilter are taken into account. * @param localID remote file name of the resource * @param populate true: populates all data fields (for instance, contact or event details); * false: only remote file name and ETag are populated