package co.smartreceipts.android.persistence.database.controllers.alterations; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.google.common.base.Preconditions; import java.io.File; import java.io.IOException; import java.util.List; import co.smartreceipts.android.date.DateUtils; import co.smartreceipts.android.model.Trip; import co.smartreceipts.android.persistence.DatabaseHelper; import co.smartreceipts.android.persistence.PersistenceManager; import co.smartreceipts.android.persistence.database.operations.DatabaseOperationMetadata; import co.smartreceipts.android.persistence.database.operations.OperationFamilyType; import co.smartreceipts.android.persistence.database.tables.DistanceTable; import co.smartreceipts.android.persistence.database.tables.ReceiptsTable; import co.smartreceipts.android.persistence.database.tables.Table; import co.smartreceipts.android.utils.log.Logger; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; import wb.android.storage.StorageManager; public class TripTableActionAlterations extends StubTableActionAlterations<Trip> { private final Table<Trip, String> mTripsTable; private final ReceiptsTable mReceiptsTable; private final DistanceTable mDistanceTable; private final DatabaseHelper mDatabaseHelper; private final StorageManager mStorageManager; public TripTableActionAlterations(@NonNull PersistenceManager persistenceManager) { this(Preconditions.checkNotNull(persistenceManager).getDatabase(), Preconditions.checkNotNull(persistenceManager).getStorageManager()); } public TripTableActionAlterations(@NonNull DatabaseHelper databaseHelper, @NonNull StorageManager storageManager) { this(Preconditions.checkNotNull(databaseHelper).getTripsTable(), databaseHelper.getReceiptsTable(), databaseHelper.getDistanceTable(), databaseHelper, storageManager); } public TripTableActionAlterations(@NonNull Table<Trip, String> tripsTable, @NonNull ReceiptsTable receiptsTable, @NonNull DistanceTable distanceTable, @NonNull DatabaseHelper databaseHelper, @NonNull StorageManager storageManager) { mTripsTable = Preconditions.checkNotNull(tripsTable); mReceiptsTable = Preconditions.checkNotNull(receiptsTable); mDistanceTable = Preconditions.checkNotNull(distanceTable); mDatabaseHelper = Preconditions.checkNotNull(databaseHelper); mStorageManager = Preconditions.checkNotNull(storageManager); } @NonNull @Override public Single<List<Trip>> postGet(@NonNull final List<Trip> trips) { return Observable.just(trips) .flatMapIterable(trips1 -> trips1) .doOnNext(mDatabaseHelper::getTripPriceAndDailyPrice) .toList(); } @NonNull @Override public Single<Trip> postInsert(@NonNull final Trip postInsertTrip) { if (postInsertTrip == null) { return Single.error(new Exception("Post insert failed due to a null trip")); } return makeTripDirectory(postInsertTrip) .doOnError(throwable -> { mTripsTable.delete(postInsertTrip, new DatabaseOperationMetadata(OperationFamilyType.Rollback)) .subscribe(); }) .andThen(Single.just(postInsertTrip)) .doOnSuccess(trip -> backUpDatabase()); } @NonNull private Completable makeTripDirectory(@NonNull final Trip trip) { return Completable.fromAction(() -> { File directory = mStorageManager.mkdir(trip.getName()); if (directory == null) { throw new IOException("Make trip directory failed"); } }); } @NonNull @Override public Single<Trip> postUpdate(@NonNull final Trip oldTrip, @Nullable final Trip newTrip) { return Single.fromCallable(() -> { if (newTrip == null) { throw new Exception("Post update failed due to a null trip"); } newTrip.setPrice(oldTrip.getPrice()); newTrip.setDailySubTotal(oldTrip.getDailySubTotal()); return newTrip; }).doOnSuccess(trip -> { if (!oldTrip.getDirectory().equals(trip.getDirectory())) { mReceiptsTable.updateParentBlocking(oldTrip, trip); mDistanceTable.updateParentBlocking(oldTrip, trip); final File dir = mStorageManager.rename(oldTrip.getDirectory(), trip.getName()); if (dir.equals(oldTrip.getDirectory())) { Logger.error(this, "Failed to re-name the trip directory... Rolling back and throwing an exception"); mTripsTable.update(trip, oldTrip, new DatabaseOperationMetadata()).blockingGet(); mReceiptsTable.updateParentBlocking(trip, oldTrip); mDistanceTable.updateParentBlocking(trip, oldTrip); throw new IOException("Failed to create trip directory"); } } }); } @NonNull @Override public Single<Trip> postDelete(@Nullable final Trip trip) { return Single.fromCallable(() -> { if (trip == null) { throw new Exception("Post delete failed due to a null trip"); } mReceiptsTable.deleteParentBlocking(trip); mDistanceTable.deleteParentBlocking(trip); if (!mStorageManager.deleteRecursively(trip.getDirectory())) { // TODO: Create clean up script Logger.error(this, "Failed to fully delete the underlying data. Create a clean up script to fix this later"); } return trip; }); } /** * Simple utility method that takes a snapshot backup of our database after all trip "insert' */ private void backUpDatabase() { File sdDB = mStorageManager.getFile(DateUtils.getCurrentDateAsYYYY_MM_DDString() + "_" + DatabaseHelper.DATABASE_NAME + ".bak"); try { if (mStorageManager.copy(mStorageManager.getFile(DatabaseHelper.DATABASE_NAME), sdDB, true)) { Logger.info(this, "Backed up database file to: {}", sdDB.getName()); } else { Logger.error(this, "Failed to backup database: {}", sdDB.getName()); } } catch (Exception e) { Logger.error(this, "Failed to back up database", e); } } }