/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock.sync;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SyncResult;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.crypto.InvalidMacException;
import org.anhonesteffort.flock.webdav.ComponentETagPair;
import org.anhonesteffort.flock.webdav.InvalidComponentException;
import org.anhonesteffort.flock.webdav.PropertyParseException;
import org.anhonesteffort.flock.webdav.WebDavConstants;
import org.anhonesteffort.flock.webdav.caldav.CalDavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.xml.Namespace;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* Programmer: rhodey
*/
public abstract class AbstractDavSyncWorker<T> implements SyncWorker {
private static final String TAG = "org.anhonesteffort.flock.sync.AbstractDavSyncWorker";
protected Context context;
protected SyncResult result;
protected AbstractLocalComponentCollection<T> localCollection;
protected HidingDavCollection<T> remoteCollection;
protected Optional<String> localCTag = Optional.absent();
protected Optional<String> remoteCTag = Optional.absent();
public AbstractDavSyncWorker(Context context,
SyncResult result,
AbstractLocalComponentCollection<T> localCollection,
HidingDavCollection<T> remoteCollection)
{
this.context = context;
this.result = result;
this.localCollection = localCollection;
this.remoteCollection = remoteCollection;
}
protected abstract Namespace getNamespace();
protected void handleLogMessage(String message) {
Log.d(TAG, localCollection.getPath() + " - " + message);
}
@Override
public void run() {
Log.d(TAG, "now syncing local: " + localCollection.getPath() +
" with remote: " + remoteCollection.getPath());
try {
SyncWorkerUtil.handleMakeFlockCollection(localCollection, remoteCollection);
localCTag = localCollection.getCTag();
remoteCTag = remoteCollection.getCTag();
if (localCTag.isPresent())
handleLogMessage("local ctag pre push local: " + localCTag.get());
else
handleLogMessage("local ctag not present pre push local");
if (remoteCTag.isPresent())
handleLogMessage("remote ctag pre push local: " + remoteCTag.get());
else
handleLogMessage("remote ctag not present pre push local");
pushLocallyCreatedProperties(result);
pushLocallyChangedProperties(result);
pushLocallyDeletedComponents(result);
pushLocallyChangedComponents(result);
pushLocallyCreatedComponents(result);
boolean pull_remote = result.stats.numInserts > 0 ||
result.stats.numUpdates > 0 ||
result.stats.numDeletes > 0 ||
!localCTag.isPresent() ||
(remoteCTag.isPresent() && !localCTag.get().equals(remoteCTag.get()));
if (!pull_remote)
return;
remoteCTag = remoteCollection.getCTag();
pullRemotelyCreatedProperties(result);
pullRemotelyChangedProperties(result);
pullRemotelyCreatedComponents(result);
pullRemotelyChangedComponents(result);
purgeRemotelyDeletedComponents(result);
if (remoteCTag.isPresent()) {
handleLogMessage("remote ctag post pull remote: " + remoteCTag.get());
if (result.stats.numAuthExceptions > 0 ||
result.stats.numSkippedEntries > 0 ||
result.stats.numParseExceptions > 0 ||
result.stats.numIoExceptions > 0)
{
handleLogMessage("sync result has errors, will not save remote CTag to local collection");
return;
}
localCollection.setCTag(remoteCTag.get());
localCollection.commitPendingOperations();
}
else
throw new PropertyParseException("Remote collection is missing CTag, things could get funny",
remoteCollection.getPath(), CalDavConstants.PROPERTY_NAME_CTAG);
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch(IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
@Override
public void cleanup() {
remoteCollection.closeHttpConnection();
}
protected abstract Optional<String> getComponentUid(T component);
protected void pushLocallyCreatedProperties(SyncResult result) {
handleLogMessage("pushLocallyCreatedProperties()");
try {
Optional<String> localDisplayName = localCollection.getDisplayName();
if (localDisplayName.isPresent()) {
Optional<String> remoteDisplayName = remoteCollection.getHiddenDisplayName();
if (!remoteDisplayName.isPresent()) {
handleLogMessage("remote display name not present, setting using local");
remoteCollection.setHiddenDisplayName(localDisplayName.get());
result.stats.numInserts++;
}
}
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (InvalidMacException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pushLocallyChangedProperties(SyncResult result) {
handleLogMessage("pushLocallyChangedProperties()");
try {
if (localCTag.isPresent() && remoteCTag.isPresent() && localCTag.get().equals(remoteCTag.get())) {
Optional<String> localDisplayName = localCollection.getDisplayName();
if (localDisplayName.isPresent()) {
Optional<String> remoteDisplayName = remoteCollection.getHiddenDisplayName();
if (remoteDisplayName.isPresent() && !localDisplayName.get().equals(remoteDisplayName.get())) {
handleLogMessage("remote display name present, updating using local");
remoteCollection.setHiddenDisplayName(localDisplayName.get());
result.stats.numUpdates++;
}
}
}
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (InvalidMacException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pushLocallyDeletedComponents(SyncResult result) {
handleLogMessage("pushLocallyDeletedComponents()");
try {
List<Pair<Long, String>> deletedIds = localCollection.getDeletedComponentIds();
handleLogMessage("found " + deletedIds.size() + " locally deleted components");
for (Pair<Long, String> componentId : deletedIds) {
try {
handleLogMessage("removing remote component: (" + componentId.first + ", " + componentId.second + ")");
remoteCollection.removeComponent(componentId.second);
localCollection.removeComponent(componentId.first);
localCollection.commitPendingOperations();
result.stats.numDeletes++;
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
if (deletedIds.size() > 0)
SyncWorkerUtil.handleRefreshCollectionProperties(context, result, remoteCollection);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pushLocallyChangedComponents(SyncResult result) {
handleLogMessage("pushLocallyChangedComponents()");
try {
List<Pair<Long, String>> updatedIds = localCollection.getUpdatedComponentIds();
handleLogMessage("found " + updatedIds.size() + " locally updated components");
for (Pair<Long, String> componentId : updatedIds) {
try {
Optional<ComponentETagPair<T>> component = localCollection.getComponent(componentId.second);
if (component.isPresent()) {
handleLogMessage("updating remote component: (" + componentId.first + ", " + componentId.second + ")");
remoteCollection.updateHiddenComponent(component.get());
localCollection.cleanComponent(componentId.first);
localCollection.commitPendingOperations();
result.stats.numUpdates++;
}
else
handleLogMessage("could not get component with id " + componentId.second + " from local collection");
} catch (InvalidComponentException e) {
SyncWorkerUtil.handleException(context, e, result);
SyncWorkerUtil.handleServerRejectedLocalComponent(localCollection, componentId.first, context, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
if (updatedIds.size() > 0)
SyncWorkerUtil.handleRefreshCollectionProperties(context, result, remoteCollection);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected abstract void prePushLocallyCreatedComponent(T component);
protected void pushLocallyCreatedComponents(SyncResult result) {
handleLogMessage("pushLocallyCreatedComponents()");
try {
List<Long> newIds = localCollection.getNewComponentIds();
handleLogMessage("found " + newIds.size() + " locally created components");
for (Long componentId : newIds) {
try {
String uid = localCollection.populateComponentUid(componentId);
Optional<T> component = localCollection.getComponent(componentId);
if (component.isPresent()) {
handleLogMessage("creating remote component: (" + componentId + ", " + uid + ")");
prePushLocallyCreatedComponent(component.get());
remoteCollection.addHiddenComponent(component.get());
localCollection.cleanComponent(componentId);
localCollection.commitPendingOperations();
result.stats.numInserts++;
}
else
handleLogMessage("could not get component (" + componentId + ", " + uid + ") from local collection");
} catch (InvalidComponentException e) {
SyncWorkerUtil.handleException(context, e, result);
SyncWorkerUtil.handleServerRejectedLocalComponent(localCollection, componentId, context, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
if (e.getErrorCode() == WebDavConstants.SC_PRECONDITION_FAILED)
SyncWorkerUtil.handleServerRejectedLocalComponent(localCollection, componentId, context, result);
else
SyncWorkerUtil.handleServerErrorOnPushNewLocalComponent(localCollection, componentId, context, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
SyncWorkerUtil.handleServerErrorOnPushNewLocalComponent(localCollection, componentId, context, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
if (newIds.size() > 0)
SyncWorkerUtil.handleRefreshCollectionProperties(context, result, remoteCollection);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pullRemotelyCreatedProperties(SyncResult result) {
handleLogMessage("pullRemotelyCreatedProperties()");
try {
Optional<String> remoteDisplayName = remoteCollection.getHiddenDisplayName();
if (remoteDisplayName.isPresent()) {
Optional<String> localDisplayName = localCollection.getDisplayName();
if (!localDisplayName.isPresent()) {
handleLogMessage("local display name not present, setting using remote");
localCollection.setDisplayName(remoteDisplayName.get());
localCollection.commitPendingOperations();
result.stats.numInserts++;
}
}
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (InvalidMacException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pullRemotelyChangedProperties(SyncResult result) {
handleLogMessage("pullRemotelyChangedProperties()");
try {
if (localCTag.isPresent() && remoteCTag.isPresent() && !localCTag.get().equals(remoteCTag.get())) {
Optional<String> remoteDisplayName = remoteCollection.getHiddenDisplayName();
if (remoteDisplayName.isPresent()) {
Optional<String> localDisplayName = localCollection.getDisplayName();
if (localDisplayName.isPresent() && !localDisplayName.get().equals(remoteDisplayName.get())) {
handleLogMessage("local display name present, updating using remote");
localCollection.setDisplayName(remoteDisplayName.get());
localCollection.commitPendingOperations();
result.stats.numUpdates++;
}
}
}
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (InvalidMacException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pullRemotelyCreatedComponents(SyncResult result) {
handleLogMessage("pullRemotelyCreatedComponents()");
List<ComponentETagPair<T>> retryList = new LinkedList<ComponentETagPair<T>>();
try {
HashMap<String, String> remoteETagMap = remoteCollection.getComponentETags();
List<String> uidsMissingLocally = SyncWorkerUtil.handleFilterUidsMissingLocally(localCollection, remoteETagMap.keySet());
List<List<String>> reportLists = SyncWorkerUtil.handlePartitionUidsForReports(uidsMissingLocally);
handleLogMessage("found " + remoteETagMap.size() + " remote components, " + uidsMissingLocally.size() +
" are missing locally, will require " + reportLists.size() + " multi-get report(s)");
for (List<String> uidsForReport : reportLists) {
try {
DecryptedMultiStatusResult<T> remoteComponents = remoteCollection.getHiddenComponents(uidsForReport);
SyncWorkerUtil.handleDoStuffWithMultiStatusResult(uidsForReport, remoteComponents, context, result);
for (ComponentETagPair<T> remoteComponent : remoteComponents.getComponentETagPairs()) {
try {
Optional<String> componentUid = getComponentUid(remoteComponent.getComponent());
if (componentUid.isPresent()) {
try {
handleLogMessage("creating local component " + componentUid.get() + " using remote");
localCollection.addComponent(remoteComponent);
localCollection.commitPendingOperations();
result.stats.numInserts++;
} catch (InvalidComponentException e) {
handleLogMessage("caught invalid component exception, could be a recurrence exception " +
"who's parent has yet to get pulled down, will retry.");
retryList.add(remoteComponent);
}
}
else
throw new InvalidRemoteComponentException("remote component is missing UID",
getNamespace(), remoteCollection.getPath());
} catch (InvalidRemoteComponentException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
for (ComponentETagPair<T> retryComponent : retryList) {
Optional<String> componentUid = getComponentUid(retryComponent.getComponent());
try {
if (componentUid.isPresent()) {
handleLogMessage("retying creation of local component " + componentUid.get() + " using remote");
localCollection.addComponent(retryComponent);
localCollection.commitPendingOperations();
result.stats.numInserts++;
}
else
throw new InvalidRemoteComponentException("retry remote component is missing UID",
getNamespace(), remoteCollection.getPath());
} catch (InvalidRemoteComponentException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void pullRemotelyChangedComponents(SyncResult result) {
handleLogMessage("pullRemotelyChangedComponents()");
try {
HashMap<String, String> remoteETagMap = remoteCollection.getComponentETags();
HashMap<String, Optional<String>> changedETagMap = SyncWorkerUtil.handleFilterUidsChangedRemotely(localCollection, remoteETagMap);
List<List<String>> reportLists = SyncWorkerUtil.handlePartitionUidsForReports(changedETagMap.keySet());
handleLogMessage("found " + remoteETagMap.size() + " remote components, " + changedETagMap.size() +
" are updated remotely, will require " + reportLists.size() + " multi-get report(s)");
for (List<String> uidsForReport : reportLists) {
try {
DecryptedMultiStatusResult<T> remoteComponents = remoteCollection.getHiddenComponents(uidsForReport);
SyncWorkerUtil.handleDoStuffWithMultiStatusResult(uidsForReport, remoteComponents, context, result);
for (ComponentETagPair<T> remoteComponent : remoteComponents.getComponentETagPairs()) {
try {
Optional<String> componentUid = getComponentUid(remoteComponent.getComponent());
if (componentUid.isPresent()) {
handleLogMessage("updating local component " + componentUid.get() + " using remote");
localCollection.updateComponent(remoteComponent);
localCollection.commitPendingOperations();
result.stats.numUpdates++;
}
else
throw new InvalidRemoteComponentException("remote component is missing UID",
getNamespace(), remoteCollection.getPath());
} catch (InvalidRemoteComponentException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
protected void purgeRemotelyDeletedComponents(SyncResult result) {
handleLogMessage("pullRemotelyDeletedComponents()");
try {
HashMap<String, String> localETagMap = localCollection.getComponentETags();
HashMap<String, String> remoteETagMap = remoteCollection.getComponentETags();
List<String> componentsMissingRemotely = new LinkedList<String>();
for (java.util.Map.Entry<String, String> localETagEntry : localETagMap.entrySet()) {
boolean found_remotely = false;
for (java.util.Map.Entry<String, String> remoteETagEntry : remoteETagMap.entrySet()) {
if (localETagEntry.getKey().equals(remoteETagEntry.getKey()))
found_remotely = true;
}
if (!found_remotely)
componentsMissingRemotely.add(localETagEntry.getKey());
}
handleLogMessage("found " + componentsMissingRemotely.size() + " local components missing remotely");
for (String remoteUid : componentsMissingRemotely) {
try {
handleLogMessage("deleting local component " + remoteUid + " missing from remote");
localCollection.removeComponent(remoteUid);
localCollection.commitPendingOperations();
result.stats.numDeletes++;
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (OperationApplicationException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
} catch (DavException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
}