package org.ovirt.mobile.movirt.sync;
import android.accounts.Account;
import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import com.android.internal.util.Predicate;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;
import org.ovirt.mobile.movirt.Broadcasts;
import org.ovirt.mobile.movirt.auth.properties.manager.AccountPropertiesManager;
import org.ovirt.mobile.movirt.facade.EntityFacade;
import org.ovirt.mobile.movirt.facade.EntityFacadeLocator;
import org.ovirt.mobile.movirt.model.Cluster;
import org.ovirt.mobile.movirt.model.DataCenter;
import org.ovirt.mobile.movirt.model.Disk;
import org.ovirt.mobile.movirt.model.Host;
import org.ovirt.mobile.movirt.model.StorageDomain;
import org.ovirt.mobile.movirt.model.Vm;
import org.ovirt.mobile.movirt.model.base.OVirtEntity;
import org.ovirt.mobile.movirt.model.base.SnapshotEmbeddableEntity;
import org.ovirt.mobile.movirt.model.mapping.EntityMapper;
import org.ovirt.mobile.movirt.model.trigger.Trigger;
import org.ovirt.mobile.movirt.provider.ProviderFacade;
import org.ovirt.mobile.movirt.rest.SimpleResponse;
import org.ovirt.mobile.movirt.rest.client.OVirtClient;
import org.ovirt.mobile.movirt.ui.MainActivityFragments;
import org.ovirt.mobile.movirt.ui.MainActivity_;
import org.ovirt.mobile.movirt.util.NotificationHelper;
import org.ovirt.mobile.movirt.util.message.MessageHelper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@EBean(scope = EBean.Scope.Singleton)
public class SyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = SyncAdapter.class.getSimpleName();
private static AtomicBoolean inSync = new AtomicBoolean();
@RootContext
Context context;
@Bean
OVirtClient oVirtClient;
@Bean
ProviderFacade provider;
@Bean
EntityFacadeLocator entityFacadeLocator;
@Bean
EventsHandler eventsHandler;
@Bean
AccountPropertiesManager propertiesManager;
@Bean
NotificationHelper notificationHelper;
@Bean
MessageHelper messageHelper;
public SyncAdapter(Context context) {
super(context, true);
}
private static <E extends OVirtEntity> Map<String, E> groupEntitiesById(List<E> entities) {
Map<String, E> entityMap = new HashMap<>();
for (E entity : entities) {
entityMap.put(entity.getId(), entity);
}
return entityMap;
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
if (!propertiesManager.accountConfigured()) {
Log.d(TAG, "Account not configured, not performing sync");
return;
}
if (inSync.compareAndSet(false, true)) {
try {
sendSyncIntent(true);
updateClusters();
updateDataCenters();
facadeSync(Vm.class);
facadeSync(Host.class);
facadeSync(StorageDomain.class);
facadeSync(Disk.class);
eventsHandler.updateEvents(false);
} catch (Exception e) {
messageHelper.showError(e);
} finally {
inSync.set(false);
sendSyncIntent(false);
}
}
}
private void updateClusters() {
oVirtClient.getClusters(getUpdateEntitiesResponse(Cluster.class));
}
private void updateDataCenters() {
oVirtClient.getDataCenters(getUpdateEntitiesResponse(DataCenter.class));
}
private <E extends OVirtEntity> void facadeSync(final Class<E> clazz) {
final EntityFacade<E> entityFacade = entityFacadeLocator.getFacade(clazz);
entityFacade.syncAll();
}
public <E extends OVirtEntity> SimpleResponse<E> getUpdateEntityResponse(final Class<E> clazz) {
return new SimpleResponse<E>() {
@Override
public void onResponse(E entity) throws RemoteException {
updateLocalEntity(entity, clazz);
}
};
}
public <E extends OVirtEntity> void updateLocalEntity(E entity, final Class<E> clazz) {
final EntityFacade<E> entityFacade = entityFacadeLocator.getFacade(clazz);
final ProviderFacade.BatchBuilder batch = provider.batch();
Collection<Trigger<E>> allTriggers = entityFacade.getAllTriggers();
updateLocalEntity(entity, clazz, allTriggers, batch);
applyBatch(batch);
}
public <E extends OVirtEntity> SimpleResponse<List<E>> getUpdateEntitiesResponse(final Class<E> clazz) {
return new SimpleResponse<List<E>>() {
@Override
public void onResponse(List<E> entities) throws RemoteException {
updateLocalEntities(entities, clazz);
}
};
}
public <E extends OVirtEntity> SimpleResponse<List<E>> getUpdateEntitiesResponse(final Class<E> clazz, final Predicate<E> scopePredicate) {
return getUpdateEntitiesResponse(clazz, scopePredicate, true);
}
public <E extends OVirtEntity> SimpleResponse<List<E>> getUpdateEntitiesResponse(final Class<E> clazz, final Predicate<E> scopePredicate,
final boolean removeExpiredEntities) {
return new SimpleResponse<List<E>>() {
@Override
public void onResponse(List<E> entities) throws RemoteException {
updateLocalEntities(entities, clazz, scopePredicate, removeExpiredEntities);
}
};
}
private void applyBatch(ProviderFacade.BatchBuilder batch) {
if (batch.isEmpty()) {
Log.d(TAG, "No updates necessary");
} else {
Log.d(TAG, "Applying batch update");
batch.apply();
}
}
public <E extends OVirtEntity> void updateLocalEntities(List<E> remoteEntities, Class<E> clazz) throws RemoteException {
updateLocalEntities(remoteEntities, clazz, new Predicate<E>() {
@Override
public boolean apply(E entity) {
return true;
}
});
}
public <E extends OVirtEntity> void updateLocalEntities(List<E> remoteEntities, Class<E> clazz, Predicate<E> scopePredicate)
throws RemoteException {
updateLocalEntities(remoteEntities, clazz, scopePredicate, true);
}
public <E extends OVirtEntity> void updateLocalEntities(List<E> remoteEntities, Class<E> clazz, Predicate<E> scopePredicate,
boolean removeExpiredEntities) throws RemoteException {
final Map<String, E> remoteEntityMap = groupEntitiesById(remoteEntities);
final EntityMapper<E> mapper = EntityMapper.forEntity(clazz);
final EntityFacade<E> entityFacade = entityFacadeLocator.getFacade(clazz);
Collection<Trigger<E>> allTriggers = new ArrayList<>();
if (entityFacade != null) {
allTriggers = entityFacade.getAllTriggers();
}
final Cursor cursor = provider.query(clazz).asCursor();
if (cursor == null) {
return;
}
ProviderFacade.BatchBuilder batch = provider.batch();
List<Pair<E, E>> entities = new ArrayList<>();
while (cursor.moveToNext()) {
E localEntity = mapper.fromCursor(cursor);
if (scopePredicate.apply(localEntity)) {
E remoteEntity = remoteEntityMap.get(localEntity.getId());
if (remoteEntity == null) { // local entity obsolete, schedule delete from db
if (removeExpiredEntities) { // except for partial updates
Log.d(TAG, String.format("%s: scheduling delete for URI = %s", clazz.getSimpleName(), localEntity.getUri()));
batch.delete(localEntity);
}
} else { // existing entity, update stats if changed
remoteEntityMap.remove(localEntity.getId());
entities.add(new Pair<>(localEntity, remoteEntity));
}
}
}
checkEntitiesChanged(entities, entityFacade, allTriggers, batch);
cursor.close();
for (E entity : remoteEntityMap.values()) {
Log.d(TAG, String.format("%s: scheduling insert for id = %s", clazz.getSimpleName(), entity.getId()));
batch.insert(entity);
}
applyBatch(batch);
}
private <E extends OVirtEntity> void updateLocalEntity(E remoteEntity, Class<E> clazz, Collection<Trigger<E>> allTriggers, ProviderFacade.BatchBuilder batch) {
final EntityFacade<E> triggerResolver = entityFacadeLocator.getFacade(clazz);
Collection<E> localEntities = provider.query(clazz).id(remoteEntity.getId()).all();
if (localEntities.isEmpty()) {
Log.d(TAG, String.format("%s: scheduling insert for id = %s", clazz.getSimpleName(), remoteEntity.getId()));
batch.insert(remoteEntity);
} else {
E localEntity = localEntities.iterator().next();
checkEntityChanged(localEntity, remoteEntity, triggerResolver, allTriggers, batch);
}
}
private <E extends OVirtEntity> void checkEntityChanged(E localEntity, E remoteEntity, EntityFacade<E> entityFacade, Collection<Trigger<E>> allTriggers, ProviderFacade.BatchBuilder batch) {
List<Pair<E, E>> entities = new ArrayList<>();
entities.add(new Pair<>(localEntity, remoteEntity));
checkEntitiesChanged(entities, entityFacade, allTriggers, batch);
}
private <E extends OVirtEntity> void checkEntitiesChanged(List<Pair<E, E>> entities, EntityFacade<E> entityFacade, Collection<Trigger<E>> allTriggers, ProviderFacade.BatchBuilder batch) {
List<Pair<E, Trigger<E>>> entitiesAndTriggers = new ArrayList<>();
for (Pair<E, E> pair : entities) {
E localEntity = pair.first;
E remoteEntity = pair.second;
if (!localEntity.equals(remoteEntity)) {
boolean processTriggers = true;
if (remoteEntity instanceof SnapshotEmbeddableEntity) {
SnapshotEmbeddableEntity snapshotEmbeddableEntity = (SnapshotEmbeddableEntity) localEntity;
processTriggers = !snapshotEmbeddableEntity.isSnapshotEmbedded();
}
if (processTriggers && entityFacade != null) {
final List<Trigger<E>> triggers = entityFacade.getTriggers(localEntity, allTriggers);
Log.d(TAG, String.format("%s: processing triggers for id = %s", localEntity.getClass().getSimpleName(), remoteEntity.getId()));
for (Trigger<E> trigger : triggers) {
if (!trigger.getCondition().evaluate(localEntity) && trigger.getCondition().evaluate(remoteEntity)) {
entitiesAndTriggers.add(new Pair<>(remoteEntity, trigger));
}
}
}
Log.d(TAG, String.format("%s: scheduling update for URI = %s", localEntity.getClass().getSimpleName(), localEntity.getUri()));
batch.update(remoteEntity);
}
}
displayNotification(entitiesAndTriggers, entityFacade);
}
private <E extends OVirtEntity> void displayNotification(List<Pair<E, Trigger<E>>> entitiesAndTriggers, EntityFacade<E> entityFacade) {
if (entitiesAndTriggers.size() == 0) {
return;
}
Intent resultIntent;
if (entitiesAndTriggers.size() == 1) {
E entity = entitiesAndTriggers.get(0).first;
final Context appContext = getContext().getApplicationContext();
resultIntent = entityFacade.getDetailIntent(entity, appContext);
resultIntent.setData(entity.getUri());
} else {
resultIntent = new Intent(context, MainActivity_.class);
resultIntent.setAction(MainActivityFragments.VMS.name());
resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
notificationHelper.showTriggersNotification(
entitiesAndTriggers, context, PendingIntent.getActivity(context, 0, resultIntent, 0)
);
}
private void sendSyncIntent(boolean sync) {
Intent intent = new Intent(Broadcasts.IN_SYNC);
intent.putExtra(Broadcasts.Extras.SYNCING, sync);
context.sendBroadcast(intent);
}
}