package co.smartreceipts.android.sync.widget.backups;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import com.google.common.base.Preconditions;
import com.hadisatrio.optional.Optional;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import co.smartreceipts.android.persistence.DatabaseHelper;
import co.smartreceipts.android.sync.BackupProvidersManager;
import co.smartreceipts.android.sync.model.RemoteBackupMetadata;
import co.smartreceipts.android.sync.network.NetworkManager;
import co.smartreceipts.android.sync.provider.SyncProvider;
import co.smartreceipts.android.utils.FileUtils;
import co.smartreceipts.android.utils.cache.SmartReceiptsTemporaryFileCache;
import co.smartreceipts.android.utils.log.Logger;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.ReplaySubject;
import wb.android.storage.StorageManager;
public class RemoteBackupsDataCache {
private final Context mContext;
private final BackupProvidersManager mBackupProvidersManager;
private final NetworkManager mNetworkManager;
private final DatabaseHelper mDatabaseHelper;
private RemoteBackupsResultsCacheHeadlessFragment mHeadlessFragment;
public RemoteBackupsDataCache(@NonNull FragmentManager fragmentManager, @NonNull Context context,
@NonNull BackupProvidersManager backupProvidersManager, @NonNull NetworkManager networkManager,
@NonNull DatabaseHelper databaseHelper) {
mContext = Preconditions.checkNotNull(context.getApplicationContext());
mBackupProvidersManager = Preconditions.checkNotNull(backupProvidersManager);
mNetworkManager = Preconditions.checkNotNull(networkManager);
mDatabaseHelper = Preconditions.checkNotNull(databaseHelper);
Preconditions.checkNotNull(fragmentManager);
RemoteBackupsResultsCacheHeadlessFragment headlessFragment = (RemoteBackupsResultsCacheHeadlessFragment) fragmentManager.findFragmentByTag(RemoteBackupsResultsCacheHeadlessFragment.TAG);
if (headlessFragment == null) {
headlessFragment = new RemoteBackupsResultsCacheHeadlessFragment();
fragmentManager.beginTransaction().add(headlessFragment, RemoteBackupsResultsCacheHeadlessFragment.TAG).commit();
}
mHeadlessFragment = headlessFragment;
}
@NonNull
public synchronized Observable<List<RemoteBackupMetadata>> getBackups(@NonNull SyncProvider syncProvider) {
if (mHeadlessFragment.getBackupsReplaySubjectMap == null) {
mHeadlessFragment.getBackupsReplaySubjectMap = new HashMap<>();
}
ReplaySubject<List<RemoteBackupMetadata>> backupsReplaySubject = mHeadlessFragment.getBackupsReplaySubjectMap.get(syncProvider);
if (backupsReplaySubject == null) {
backupsReplaySubject = ReplaySubject.create();
mBackupProvidersManager.getRemoteBackups()
.onErrorResumeNext(throwable -> mNetworkManager.getNetworkStateChangeObservable()
.filter(hasNetwork -> hasNetwork)
.firstOrError()
.flatMap(hasNetwork -> mBackupProvidersManager.getRemoteBackups()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.toObservable()
.subscribe(backupsReplaySubject);
mHeadlessFragment.getBackupsReplaySubjectMap.put(syncProvider, backupsReplaySubject);
}
return backupsReplaySubject;
}
public synchronized void clearGetBackupsResults() {
if (mHeadlessFragment.getBackupsReplaySubjectMap != null) {
mHeadlessFragment.getBackupsReplaySubjectMap.clear();
}
}
@NonNull
public synchronized Observable<Boolean> deleteBackup(@Nullable RemoteBackupMetadata remoteBackupMetadata) {
if (mHeadlessFragment.deleteBackupReplaySubjectMap == null) {
mHeadlessFragment.deleteBackupReplaySubjectMap = new HashMap<>();
}
ReplaySubject<Boolean> deleteReplaySubject = mHeadlessFragment.deleteBackupReplaySubjectMap.get(remoteBackupMetadata);
if (deleteReplaySubject == null) {
deleteReplaySubject = ReplaySubject.create();
getBackupMetadata(remoteBackupMetadata)
.flatMap(remoteBackupMetadataOptional -> deleteLocalSyncDataIfNeeded(remoteBackupMetadataOptional.isPresent() ? remoteBackupMetadataOptional.get() : null)
.flatMap(success -> {
if (success) {
if (remoteBackupMetadata == null) {
Logger.info(RemoteBackupsDataCache.this, "Clearing current configuration");
return mBackupProvidersManager.clearCurrentBackupConfiguration();
} else {
Logger.info(RemoteBackupsDataCache.this, "Deleting provided metadata");
return mBackupProvidersManager.deleteBackup(remoteBackupMetadata);
}
} else {
return Single.just(false);
}
}))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.toObservable()
.subscribe(deleteReplaySubject);
mHeadlessFragment.deleteBackupReplaySubjectMap.put(remoteBackupMetadata, deleteReplaySubject);
}
return deleteReplaySubject;
}
@NonNull
public synchronized Observable<Boolean> restoreBackup(@NonNull final RemoteBackupMetadata remoteBackupMetadata, final boolean overwriteExistingData) {
if (mHeadlessFragment.restoreBackupReplaySubjectMap == null) {
mHeadlessFragment.restoreBackupReplaySubjectMap = new HashMap<>();
}
ReplaySubject<Boolean> restoreBackupReplaySubject = mHeadlessFragment.restoreBackupReplaySubjectMap.get(remoteBackupMetadata);
if (restoreBackupReplaySubject == null) {
restoreBackupReplaySubject = ReplaySubject.create();
mBackupProvidersManager.restoreBackup(remoteBackupMetadata, overwriteExistingData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.toObservable()
.subscribe(restoreBackupReplaySubject);
mHeadlessFragment.restoreBackupReplaySubjectMap.put(remoteBackupMetadata, restoreBackupReplaySubject);
}
return restoreBackupReplaySubject;
}
@NonNull
public synchronized Observable<File> downloadBackup(@NonNull final RemoteBackupMetadata remoteBackupMetadata, final boolean debugMode) {
if (mHeadlessFragment.downloadBackupReplaySubjectMap == null) {
mHeadlessFragment.downloadBackupReplaySubjectMap = new HashMap<>();
}
ReplaySubject<File> downloadBackupReplaySubjectMap = mHeadlessFragment.downloadBackupReplaySubjectMap.get(remoteBackupMetadata);
if (downloadBackupReplaySubjectMap == null) {
final File cacheDir = new SmartReceiptsTemporaryFileCache(mContext).getFile(FileUtils.omitIllegalCharactersFromFileName(remoteBackupMetadata.getSyncDeviceName()));
final File cacheDirZipFile = new SmartReceiptsTemporaryFileCache(mContext).getFile(FileUtils.omitIllegalCharactersFromFileName(remoteBackupMetadata.getSyncDeviceName()) + ".zip");
downloadBackupReplaySubjectMap = ReplaySubject.create();
Completable.fromCallable(() -> {
final StorageManager storageManager = StorageManager.getInstance(mContext);
if (storageManager.deleteRecursively(cacheDir)) {
if ((cacheDir.exists() || cacheDir.mkdirs()) && (!cacheDirZipFile.exists() || cacheDirZipFile.delete())) {
return true;
} else {
throw new IOException("Failed to create cache directory to save the images");
}
} else {
throw new IOException("Failed delete our previously cached directory");
}
})
.andThen(downloadData(remoteBackupMetadata, debugMode, cacheDir))
.map(files -> StorageManager.getInstance(mContext).zip(cacheDir))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.toObservable()
.subscribe(downloadBackupReplaySubjectMap);
mHeadlessFragment.downloadBackupReplaySubjectMap.put(remoteBackupMetadata, downloadBackupReplaySubjectMap);
}
return downloadBackupReplaySubjectMap;
}
@NonNull
private Single<List<File>> downloadData(@NonNull RemoteBackupMetadata remoteBackupMetadata, boolean debugMode, File cacheDir) {
if (debugMode) {
return mBackupProvidersManager.debugDownloadAllData(remoteBackupMetadata, cacheDir);
} else {
return mBackupProvidersManager.downloadAllData(remoteBackupMetadata, cacheDir);
}
}
public synchronized void removeCachedRestoreBackupFor(@NonNull final RemoteBackupMetadata remoteBackupMetadata) {
if (mHeadlessFragment.restoreBackupReplaySubjectMap != null) {
mHeadlessFragment.restoreBackupReplaySubjectMap.remove(remoteBackupMetadata);
}
if (mHeadlessFragment.downloadBackupReplaySubjectMap != null) {
mHeadlessFragment.downloadBackupReplaySubjectMap.remove(remoteBackupMetadata);
}
}
@NonNull
private Single<Optional<RemoteBackupMetadata>> getBackupMetadata(@Nullable RemoteBackupMetadata remoteBackupMetadata) {
if (remoteBackupMetadata == null) {
Logger.debug(this, "Attempting to fetch the local metadata");
return mBackupProvidersManager.getRemoteBackups()
.map(remoteBackupMetadatas -> {
for (final RemoteBackupMetadata metadata : remoteBackupMetadatas) {
if (metadata.getSyncDeviceId().equals(mBackupProvidersManager.getDeviceSyncId())) {
Logger.info(this, "Identified the local device metadata as {}", metadata);
return Optional.of(metadata);
}
}
Logger.warn(this, "No local backup currently exists.");
return Optional.absent();
});
} else {
Logger.debug(this, "Returning the provided metadata: {}", remoteBackupMetadata);
return Single.just(Optional.of(remoteBackupMetadata));
}
}
@NonNull
private Single<Boolean> deleteLocalSyncDataIfNeeded(@Nullable final RemoteBackupMetadata remoteBackupMetadata) {
if (remoteBackupMetadata == null || remoteBackupMetadata.getSyncDeviceId().equals(mBackupProvidersManager.getDeviceSyncId())) {
return mDatabaseHelper.getReceiptsTable().deleteSyncData(mBackupProvidersManager.getSyncProvider());
} else {
return Single.just(true);
}
}
public static final class RemoteBackupsResultsCacheHeadlessFragment extends Fragment {
private static final String TAG = RemoteBackupsDataCache.class.getName();
private Map<SyncProvider, ReplaySubject<List<RemoteBackupMetadata>>> getBackupsReplaySubjectMap;
private Map<RemoteBackupMetadata, ReplaySubject<Boolean>> deleteBackupReplaySubjectMap;
private Map<RemoteBackupMetadata, ReplaySubject<Boolean>> restoreBackupReplaySubjectMap;
private Map<RemoteBackupMetadata, ReplaySubject<File>> downloadBackupReplaySubjectMap;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}