/*
* *
* 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.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.DavAccountHelper;
import org.anhonesteffort.flock.NotificationDrawer;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.crypto.InvalidMacException;
import org.anhonesteffort.flock.crypto.KeyHelper;
import org.anhonesteffort.flock.crypto.MasterCipher;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.webdav.PropertyParseException;
import org.apache.jackrabbit.webdav.DavException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Programmer: rhodey
*/
public abstract class AbstractSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = "org.anhonesteffort.flock.sync.AbstractSyncAdapter";
protected ContentProviderClient provider;
protected SyncResult syncResult;
protected DavAccount davAccount;
protected MasterCipher masterCipher;
public AbstractSyncAdapter(Context context) {
super(context, true);
}
protected boolean syncIntervalHasPassed() {
long syncIntervalMs = getSyncScheduler().getSyncIntervalMinutes() * 60 * 1000;
Optional<Long> timeLastSyncMs = getSyncScheduler().getTimeLastSync();
long timeNowMs = new Date().getTime();
return !timeLastSyncMs.isPresent() || (timeNowMs - syncIntervalMs) >= timeLastSyncMs.get();
}
protected abstract AbstractSyncScheduler getSyncScheduler();
protected abstract boolean localHasChanged() throws RemoteException;
protected abstract void handlePreSyncOperations()
throws PropertyParseException, InvalidMacException, DavException,
RemoteException, GeneralSecurityException, IOException;
protected abstract List<SyncWorker> getSyncWorkers(boolean localChangesOnly)
throws DavException, RemoteException, RegistrationApiException, IOException;
protected abstract void handlePostSyncOperations()
throws PropertyParseException, InvalidMacException, DavException,
RemoteException, GeneralSecurityException, IOException;
@Override
public void onPerformSync(Account account,
Bundle extras,
String authority,
ContentProviderClient provider,
SyncResult syncResult)
{
Log.d(TAG, "performing sync for authority >> " + authority);
this.provider = provider;
this.syncResult = syncResult;
syncResult.stats.numAuthExceptions =
syncResult.stats.numConflictDetectedExceptions =
syncResult.stats.numIoExceptions =
syncResult.stats.numParseExceptions = 0;
if (!getSyncScheduler().getIsSyncEnabled(account)) {
Log.w(TAG, "sync disabled, not gonna sync " + authority);
return;
}
Optional<DavAccount> davAccountOptional = DavAccountHelper.getAccount(getContext());
if (!davAccountOptional.isPresent()) {
Log.d(TAG, "dav account is missing, not gonna sync " + authority);
syncResult.stats.numAuthExceptions++;
showNotifications(syncResult);
return ;
}
davAccount = davAccountOptional.get();
try {
Optional<MasterCipher> masterCipherOptional = KeyHelper.getMasterCipher(getContext());
if (!masterCipherOptional.isPresent()) {
Log.d(TAG, "master cipher is missing, not gonna sync " + authority);
syncResult.stats.numAuthExceptions++;
showNotifications(syncResult);
return;
}
masterCipher = masterCipherOptional.get();
boolean forceSync = extras.getBoolean(AbstractSyncScheduler.EXTRA_FORCE_SYNC, false);
if (!forceSync && !syncIntervalHasPassed() && !localHasChanged()) {
Log.d(TAG, "sync is not forced, interval has not passed, and local has not changed. " +
"not gonna sync " + authority);
return ;
}
handlePreSyncOperations();
List<SyncWorker> workers = getSyncWorkers(!forceSync && !syncIntervalHasPassed());
if (workers.size() > 0) {
Log.d(TAG, "starting thread executor service for " + workers.size() + " " +
authority + " sync workers.");
ExecutorService executor = Executors.newFixedThreadPool(workers.size());
for (SyncWorker worker : workers)
executor.execute(worker);
executor.shutdown();
executor.awaitTermination(60, TimeUnit.MINUTES);
for (SyncWorker worker : workers)
worker.cleanup();
}
handlePostSyncOperations();
getSyncScheduler().setTimeLastSync(new Date().getTime());
} catch (PropertyParseException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (DavException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (RemoteException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (RegistrationApiException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (InvalidMacException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (GeneralSecurityException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (IOException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
} catch (InterruptedException e) {
SyncWorkerUtil.handleException(getContext(), e, syncResult);
}
Log.d(TAG, "completed sync for authority >> " + authority);
showNotifications(syncResult);
}
private void showNotifications(SyncResult result) {
if (result.stats.numAuthExceptions > 0 &&
!NotificationDrawer.isAuthNotificationDisabled(getContext(), getSyncScheduler().getAuthority()))
{
NotificationDrawer.handleInvalidatePasswordAndShowAuthNotification(getContext());
}
if (NotificationDrawer.isAuthNotificationDisabled(getContext(), getSyncScheduler().getAuthority()))
NotificationDrawer.enableAuthNotifications(getContext(), getSyncScheduler().getAuthority());
}
}