package co.smartreceipts.android.sync.drive.managers;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import co.smartreceipts.android.analytics.Analytics;
import co.smartreceipts.android.analytics.events.ErrorEvent;
import co.smartreceipts.android.model.Receipt;
import co.smartreceipts.android.model.factory.ReceiptBuilderFactoryFactory;
import co.smartreceipts.android.persistence.database.controllers.TableController;
import co.smartreceipts.android.persistence.database.operations.DatabaseOperationMetadata;
import co.smartreceipts.android.persistence.database.operations.OperationFamilyType;
import co.smartreceipts.android.persistence.database.tables.ReceiptsTable;
import co.smartreceipts.android.sync.drive.rx.DriveStreamMappings;
import co.smartreceipts.android.sync.drive.rx.DriveStreamsManager;
import co.smartreceipts.android.sync.model.SyncState;
import co.smartreceipts.android.sync.network.NetworkManager;
import co.smartreceipts.android.sync.provider.SyncProvider;
import co.smartreceipts.android.utils.log.Logger;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
public class DriveReceiptsManager {
private final TableController<Receipt> mReceiptTableController;
private final ReceiptsTable mReceiptsTable;
private final DriveStreamsManager mDriveTaskManager;
private final DriveDatabaseManager mDriveDatabaseManager;
private final NetworkManager mNetworkManager;
private final Analytics mAnalytics;
private final DriveStreamMappings mDriveStreamMappings;
private final ReceiptBuilderFactoryFactory mReceiptBuilderFactoryFactory;
private final Scheduler mObserveOnScheduler;
private final Scheduler mSubscribeOnScheduler;
private final AtomicBoolean mIsEnabled = new AtomicBoolean(true);
private final AtomicBoolean mIsIntializing = new AtomicBoolean(false);
public DriveReceiptsManager(@NonNull TableController<Receipt> receiptsTableController, @NonNull ReceiptsTable receiptsTable,
@NonNull DriveStreamsManager driveTaskManager, @NonNull DriveDatabaseManager driveDatabaseManager,
@NonNull NetworkManager networkManager, @NonNull Analytics analytics) {
this(receiptsTableController, receiptsTable, driveTaskManager, driveDatabaseManager, networkManager, analytics, new DriveStreamMappings(), new ReceiptBuilderFactoryFactory(), Schedulers.io(), Schedulers.io());
}
public DriveReceiptsManager(@NonNull TableController<Receipt> receiptsTableController, @NonNull ReceiptsTable receiptsTable,
@NonNull DriveStreamsManager driveTaskManager, @NonNull DriveDatabaseManager driveDatabaseManager,
@NonNull NetworkManager networkManager, @NonNull Analytics analytics, @NonNull DriveStreamMappings driveStreamMappings,
@NonNull ReceiptBuilderFactoryFactory receiptBuilderFactoryFactory, @NonNull Scheduler observeOnScheduler,
@NonNull Scheduler subscribeOnScheduler) {
mReceiptTableController = Preconditions.checkNotNull(receiptsTableController);
mReceiptsTable = Preconditions.checkNotNull(receiptsTable);
mDriveTaskManager = Preconditions.checkNotNull(driveTaskManager);
mDriveDatabaseManager = Preconditions.checkNotNull(driveDatabaseManager);
mNetworkManager = Preconditions.checkNotNull(networkManager);
mAnalytics = Preconditions.checkNotNull(analytics);
mDriveStreamMappings = Preconditions.checkNotNull(driveStreamMappings);
mReceiptBuilderFactoryFactory = Preconditions.checkNotNull(receiptBuilderFactoryFactory);
mObserveOnScheduler = Preconditions.checkNotNull(observeOnScheduler);
mSubscribeOnScheduler = Preconditions.checkNotNull(subscribeOnScheduler);
}
public synchronized void initialize() {
if (mIsEnabled.get()) {
if (mNetworkManager.isNetworkAvailable()) {
if (!mIsIntializing.getAndSet(true)) {
Logger.info(this, "Performing initialization of drive receipts");
mReceiptsTable.getUnsynced(SyncProvider.GoogleDrive)
.flatMapObservable(Observable::fromIterable)
.subscribeOn(mSubscribeOnScheduler)
.observeOn(mObserveOnScheduler)
.subscribe(receipt -> {
Logger.info(DriveReceiptsManager.this, "Performing found unsynced receipt " + receipt.getId());
if (receipt.getSyncState().isMarkedForDeletion(SyncProvider.GoogleDrive)) {
Logger.info(DriveReceiptsManager.this, "Handling delete action during initialization");
handleDeleteInternal(receipt);
} else {
Logger.info(DriveReceiptsManager.this, "Handling insert/update action during initialization");
handleInsertOrUpdateInternal(receipt);
}
}, throwable -> {
mAnalytics.record(new ErrorEvent(DriveReceiptsManager.this, throwable));
Logger.error(DriveReceiptsManager.this, "Failed to fetch our unsynced receipt data", throwable);
mIsIntializing.set(false);
}, () -> {
mDriveDatabaseManager.syncDatabase();
mIsIntializing.set(false);
});
}
}
}
}
public synchronized void enable() {
Logger.info(this, "Enabling Drive Receipts Manager");
mIsEnabled.set(true);
}
public synchronized void disable() {
Logger.info(this, "Disabling Drive Receipts Manager");
mIsEnabled.set(false);
}
public synchronized void handleInsertOrUpdate(@NonNull final Receipt receipt) {
if (!mIsIntializing.get()) {
handleInsertOrUpdateInternal(receipt);
}
}
@VisibleForTesting
synchronized void handleInsertOrUpdateInternal(@NonNull final Receipt receipt) {
if (!mIsEnabled.get()) {
Logger.warn(this, "Ignoring insert or update as we're currently disabled");
return;
}
Preconditions.checkNotNull(receipt);
Preconditions.checkArgument(!receipt.getSyncState().isSynced(SyncProvider.GoogleDrive), "Cannot sync an already synced receipt");
Preconditions.checkArgument(!receipt.getSyncState().isMarkedForDeletion(SyncProvider.GoogleDrive), "Cannot insert/update a receipt that is marked for deletion");
if (mNetworkManager.isNetworkAvailable()) {
onInsertOrUpdateObservable(receipt)
.observeOn(mObserveOnScheduler)
.subscribeOn(mSubscribeOnScheduler)
.map(syncState -> mReceiptBuilderFactoryFactory.build(receipt).setSyncState(syncState).build())
.flatMapObservable(newReceipt -> {
Logger.info(DriveReceiptsManager.this, "Updating receipt " + receipt.getId() + " to reflect its sync state");
return mReceiptTableController.update(receipt, newReceipt, new DatabaseOperationMetadata(OperationFamilyType.Sync));
})
.subscribe(newReceipt -> {
Logger.info(DriveReceiptsManager.this, "Successfully updated receipt " + receipt.getId() + " to reflect its sync state");
}, throwable -> {
mAnalytics.record(new ErrorEvent(DriveReceiptsManager.this, throwable));
Logger.error(DriveReceiptsManager.this, "Failed to handle insert/update for " + receipt.getId() + " to reflect its sync state", throwable);
});
} else {
Logger.warn(this, "No network. Skipping insert/update");
}
}
public synchronized void handleDelete(@NonNull final Receipt receipt) {
if (!mIsIntializing.get()) {
handleDeleteInternal(receipt);
}
}
@VisibleForTesting
synchronized void handleDeleteInternal(@NonNull final Receipt receipt) {
if (!mIsEnabled.get()) {
Logger.warn(this, "Ignoring delete as we're currently disabled");
return;
}
Preconditions.checkNotNull(receipt);
Preconditions.checkArgument(!receipt.getSyncState().isSynced(SyncProvider.GoogleDrive), "Cannot delete an already synced receipt");
Preconditions.checkArgument(receipt.getSyncState().isMarkedForDeletion(SyncProvider.GoogleDrive), "Cannot delete a receipt that isn't marked for deletion");
if (mNetworkManager.isNetworkAvailable()) {
mDriveTaskManager.deleteDriveFile(receipt.getSyncState(), true)
.flatMapObservable(syncState -> Observable.just(mReceiptBuilderFactoryFactory.build(receipt).setSyncState(syncState).build()))
.observeOn(mObserveOnScheduler)
.subscribeOn(mSubscribeOnScheduler)
.subscribe(newReceipt -> {
Logger.info(DriveReceiptsManager.this, "Attempting to fully delete receipt " + newReceipt.getId() + " that is marked for deletion");
mReceiptTableController.delete(newReceipt, new DatabaseOperationMetadata(OperationFamilyType.Sync));
}, throwable -> {
mAnalytics.record(new ErrorEvent(DriveReceiptsManager.this, throwable));
Logger.error(DriveReceiptsManager.this, "Failed to handle delete for " + receipt.getId() + " to reflect its sync state", throwable);
});
} else {
Logger.warn(DriveReceiptsManager.this, "No network. Skipping delete");
}
}
@NonNull
private Single<SyncState> onInsertOrUpdateObservable(@NonNull final Receipt receipt) {
final SyncState oldSyncState = receipt.getSyncState();
final File receiptFile = receipt.getFile();
if (oldSyncState.getSyncId(SyncProvider.GoogleDrive) == null) {
if (receiptFile != null && receiptFile.exists()) {
Logger.info(this, "Found receipt " + receipt.getId() + " with a non-uploaded file. Uploading");
return mDriveTaskManager.uploadFileToDrive(oldSyncState, receiptFile);
} else {
Logger.info(this, "Found receipt " + receipt.getId() + " without a file. Marking as synced for Drive");
return Single.just(mDriveStreamMappings.postInsertSyncState(oldSyncState, null));
}
} else {
if (receiptFile != null) {
Logger.info(this, "Found receipt " + receipt.getId() + " with a new file. Updating");
return mDriveTaskManager.updateDriveFile(oldSyncState, receiptFile);
} else {
Logger.info(this, "Found receipt " + receipt.getId() + " with a stale file reference. Removing");
return mDriveTaskManager.deleteDriveFile(oldSyncState, false);
}
}
}
}