package co.smartreceipts.android.sync.manual;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import com.google.common.base.Preconditions;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import co.smartreceipts.android.persistence.DatabaseHelper;
import co.smartreceipts.android.persistence.PersistenceManager;
import co.smartreceipts.android.utils.log.Logger;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.ReplaySubject;
import wb.android.storage.SDCardFileManager;
import wb.android.storage.SDCardStateException;
import wb.android.storage.StorageManager;
public class ManualRestoreTask {
private final PersistenceManager mPersistenceManager;
private final Context mContext;
private final Scheduler mObserveOnScheduler;
private final Scheduler mSubscribeOnScheduler;
private final Map<RestoreRequest, ReplaySubject<Boolean>> mRestoreSubjectMap = new HashMap<>();
ManualRestoreTask(@NonNull PersistenceManager persistenceManager, @NonNull Context context) {
this(persistenceManager, context, Schedulers.io(), Schedulers.io());
}
ManualRestoreTask(@NonNull PersistenceManager persistenceManager, @NonNull Context context, @NonNull Scheduler observeOnScheduler, @NonNull Scheduler subscribeOnScheduler) {
mPersistenceManager = Preconditions.checkNotNull(persistenceManager);
mContext = Preconditions.checkNotNull(context.getApplicationContext());
mObserveOnScheduler = Preconditions.checkNotNull(observeOnScheduler);
mSubscribeOnScheduler = Preconditions.checkNotNull(subscribeOnScheduler);
}
@NonNull
public synchronized ReplaySubject<Boolean> restoreData(@NonNull final Uri uri, final boolean overwrite) {
final RestoreRequest restoreRequest = new RestoreRequest(uri, overwrite);
ReplaySubject<Boolean> restoreReplaySubject = mRestoreSubjectMap.get(restoreRequest);
if (restoreReplaySubject == null) {
restoreReplaySubject = ReplaySubject.create();
restoreDataToSingle(uri, overwrite)
.observeOn(mObserveOnScheduler)
.subscribeOn(mSubscribeOnScheduler)
.toObservable()
.subscribe(restoreReplaySubject);
mRestoreSubjectMap.put(restoreRequest, restoreReplaySubject);
}
return restoreReplaySubject;
}
public Single<Boolean> restoreDataToSingle(@NonNull final Uri uri, final boolean overwrite) {
return Single.create(emitter -> {
Logger.debug(this, "Starting log task at {}", System.currentTimeMillis());
Logger.debug(this, "Uri: {}", uri);
try {
SDCardFileManager external = mPersistenceManager.getExternalStorageManager();
if (external.getFile(ManualBackupTask.DATABASE_EXPORT_NAME).delete()); {
Logger.debug(this, "Deleting existing backup database");
}
String scheme = uri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
Logger.debug(this, "Processing URI with accepted scheme.");
InputStream is = null;
try {
ContentResolver cr = mContext.getContentResolver();
is = cr.openInputStream(uri);
File dest = external.getFile("smart.zip");
external.delete(dest);
if (!external.copy(is, dest, true)) {
Logger.debug(this, "Copy failed.");
emitter.onSuccess(false);
} else {
final boolean importResult = importAll(external, dest, overwrite);
external.delete(dest);
emitter.onSuccess(importResult);
}
}
catch (IOException e) {
Logger.error(ManualRestoreTask.this, "Caught exception during import at [1]", e);
emitter.onError(e);
}
finally {
StorageManager.closeQuietly(is);
}
}
else {
Logger.debug(this, "Processing URI with unknown scheme.");
File src = null;
File dest = external.getFile("smart.zip");
external.delete(dest);
if (uri.getPath() != null) {
src = new File(uri.getPath());
}
else if (uri.getEncodedPath() != null) {
src = new File(uri.getEncodedPath());
}
if (src == null || !src.exists()) {
Logger.debug(ManualRestoreTask.this, "Unknown source.");
emitter.onSuccess(false);
}
if (!external.copy(src, dest, true)) {
Logger.debug(ManualRestoreTask.this, "Copy failed.");
emitter.onSuccess(false);
} else {
final boolean importResult = importAll(external, dest, overwrite);
external.delete(dest);
emitter.onSuccess(importResult);
}
}
} catch (IOException | SDCardStateException e) {
Logger.error(ManualRestoreTask.this, "Caught exception during import at [2]", e);
emitter.onError(e);
}
});
}
private boolean importAll(SDCardFileManager external, File file, final boolean overwrite) {
Logger.debug(this, "import all : {} {}", file, overwrite);
if (!external.unzip(file, overwrite)) {
Logger.debug(this, "Unzip failed");
return false;
}
StorageManager internal = mPersistenceManager.getInternalStorageManager();
File sdPrefs = external.getFile("shared_prefs");
File prefs = internal.getFile(internal.getRoot().getParentFile(), "shared_prefs");
try {
if (!internal.copy(sdPrefs, prefs, overwrite)) {
Logger.error(this, "Failed to import settings");
}
}
catch (IOException e) {
Logger.error(this, "Caught IOexception during import at [3]", e.toString());
}
try {
File internalDir = external.getFile("Internal");
internal.copy(internalDir, internal.getRoot(), overwrite);
}
catch (IOException e) {
Logger.error(this, "Caught IOexception during import at [4]", e.toString());
}
DatabaseHelper db = mPersistenceManager.getDatabase();
Logger.debug(this, "Merging database");
return db.merge(external.getFile(ManualBackupTask.DATABASE_EXPORT_NAME).getAbsolutePath(), mContext.getPackageName(), overwrite);
}
private static final class RestoreRequest {
private final Uri mUri;
private final boolean mOverwrite;
public RestoreRequest(@NonNull Uri uri, boolean overwrite) {
mUri = Preconditions.checkNotNull(uri);
mOverwrite = overwrite;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RestoreRequest)) return false;
RestoreRequest that = (RestoreRequest) o;
if (mOverwrite != that.mOverwrite) return false;
return mUri.equals(that.mUri);
}
@Override
public int hashCode() {
int result = mUri.hashCode();
result = 31 * result + (mOverwrite ? 1 : 0);
return result;
}
}
}