Browse Source

Update export backup logic to use futures instead of CountDownLatch. Change backup file name to use date and time instead of milliseconds.

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
cea61eae6c
  1. 10
      app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
  2. 230
      app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java

10
app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java

@ -19,6 +19,8 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import awais.instagrabber.databinding.DialogCreateBackupBinding;
@ -32,6 +34,7 @@ import static awais.instagrabber.utils.DownloadUtils.PERMS;
public class CreateBackupDialogFragment extends DialogFragment {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final SimpleDateFormat BACKUP_FILE_DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
private final OnResultListener onResultListener;
private DialogCreateBackupBinding binding;
@ -139,7 +142,8 @@ public class CreateBackupDialogFragment extends DialogFragment {
final DirectoryChooser directoryChooser = new DirectoryChooser()
.setInitialDirectory(folderPath)
.setInteractionListener(path -> {
final File file = new File(path, String.format(Locale.ENGLISH, "barinsta_%d.backup", System.currentTimeMillis()));
final Date now = new Date();
final File file = new File(path, String.format("barinsta_%s.backup", BACKUP_FILE_DATE_TIME_FORMAT.format(now)));
int flags = 0;
if (binding.cbExportFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
@ -150,12 +154,12 @@ public class CreateBackupDialogFragment extends DialogFragment {
if (binding.cbExportLogins.isChecked()) {
flags |= ExportImportUtils.FLAG_COOKIES;
}
ExportImportUtils.exportData(password, flags, file, result -> {
ExportImportUtils.exportData(context, flags, file, password, result -> {
if (onResultListener != null) {
onResultListener.onResult(result);
}
dismiss();
}, context);
});
});
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

230
app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java

@ -2,7 +2,6 @@ package awais.instagrabber.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@ -10,8 +9,14 @@ import android.widget.Toast;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -24,7 +29,6 @@ import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.datasources.AccountDataSource;
@ -49,48 +53,6 @@ public final class ExportImportUtils {
public static final int FLAG_FAVORITES = 1 << 1;
public static final int FLAG_SETTINGS = 1 << 2;
@IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true)
@interface ExportImportFlags {}
public static void exportData(@Nullable final String password,
@ExportImportFlags final int flags,
@NonNull final File filePath,
final FetchListener<Boolean> fetchListener,
@NonNull final Context context) {
getExportString(flags, context, exportString -> {
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
});
}
public static void importData(@NonNull final Context context,
@ExportImportFlags final int flags,
@NonNull final File file,
@ -262,66 +224,116 @@ public final class ExportImportUtils {
return false;
}
//todo Need to improve logic
public static void exportData(@NonNull final Context context,
@ExportImportFlags final int flags,
@NonNull final File filePath,
final String password,
final FetchListener<Boolean> fetchListener) {
getExportString(flags, context, exportString -> {
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
});
}
private static void getExportString(@ExportImportFlags final int flags,
@NonNull final Context context,
final OnExportStringCreatedCallback callback) {
final Handler innerHandler = new Handler();
AppExecutors.getInstance().tasksThread().execute(() -> {
final CountDownLatch responseWaiter = new CountDownLatch(3);
if (callback == null) return;
try {
final ImmutableList.Builder<ListenableFuture<?>> futures = ImmutableList.builder();
futures.add((flags & FLAG_SETTINGS) == FLAG_SETTINGS
? getSettings(context)
: Futures.immediateFuture(null));
futures.add((flags & FLAG_COOKIES) == FLAG_COOKIES
? getCookies(context)
: Futures.immediateFuture(null));
futures.add((flags & FLAG_FAVORITES) == FLAG_FAVORITES
? getFavorites(context)
: Futures.immediateFuture(null));
//noinspection UnstableApiUsage
final ListenableFuture<List<Object>> allFutures = Futures.allAsList(futures.build());
Futures.addCallback(allFutures, new FutureCallback<List<Object>>() {
@Override
public void onSuccess(@NullableDecl final List<Object> result) {
final JSONObject jsonObject = new JSONObject();
innerHandler.post(() -> {
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
if (result == null) {
callback.onCreated(jsonObject.toString());
return;
}
try {
jsonObject.put("settings", getSettings(context));
} catch (JSONException e) {
Log.e(TAG, "getExportString: ", e);
final JSONObject settings = (JSONObject) result.get(0);
if (settings != null) {
jsonObject.put("settings", settings);
}
} catch (Exception e) {
Log.e(TAG, "error getting settings: ", e);
}
responseWaiter.countDown();
});
innerHandler.post(() -> {
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
getCookies(context, array -> {
try {
jsonObject.put("cookies", array);
} catch (JSONException e) {
Log.e(TAG, "error getting accounts", e);
final JSONArray accounts = (JSONArray) result.get(1);
if (accounts != null) {
jsonObject.put("cookies", accounts);
}
responseWaiter.countDown();
});
return;
} catch (Exception e) {
Log.e(TAG, "error getting accounts", e);
}
responseWaiter.countDown();
});
innerHandler.post(() -> {
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
getFavorites(context, array -> {
try {
jsonObject.put("favs", array);
} catch (JSONException e) {
Log.e(TAG, "getExportString: ", e);
final JSONArray favorites = (JSONArray) result.get(2);
if (favorites != null) {
jsonObject.put("favs", favorites);
}
responseWaiter.countDown();
});
return;
} catch (Exception e) {
Log.e(TAG, "error getting favorites: ", e);
}
responseWaiter.countDown();
});
responseWaiter.await();
callback.onCreated(jsonObject.toString());
}
@Override
public void onFailure(@NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
callback.onCreated(null);
}
}, AppExecutors.getInstance().tasksThread());
return;
} catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
callback.onCreated(null);
});
}
@NonNull
private static JSONObject getSettings(@NonNull final Context context) {
private static ListenableFuture<JSONObject> getSettings(@NonNull final Context context) {
final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
return AppExecutors.getInstance().tasksThread().submit(() -> {
final Map<String, ?> allPrefs = sharedPreferences.getAll();
if (allPrefs == null) {
return new JSONObject();
@ -336,12 +348,12 @@ public final class ExportImportUtils {
Log.e(TAG, "Error exporting settings", e);
}
return new JSONObject();
});
}
private static void getFavorites(final Context context, final OnFavoritesJsonLoadedCallback callback) {
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource);
try {
private static ListenableFuture<JSONArray> getFavorites(final Context context) {
final SettableFuture<JSONArray> future = SettableFuture.create();
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() {
@Override
public void onSuccess(final List<Favorite> favorites) {
@ -357,17 +369,6 @@ public final class ExportImportUtils {
jsonArray.put(jsonObject);
}
} catch (Exception e) {
Log.e(TAG, "onSuccess: Error creating json array", e);
}
callback.onFavoritesJsonLoaded(jsonArray);
}
@Override
public void onDataNotAvailable() {
callback.onFavoritesJsonLoaded(new JSONArray());
}
});
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
}
@ -375,15 +376,25 @@ public final class ExportImportUtils {
Log.e(TAG, "Error exporting favorites", e);
}
}
future.set(jsonArray);
}
private static void getCookies(final Context context, final OnAccountJsonLoadedCallback callback) {
@Override
public void onDataNotAvailable() {
future.set(new JSONArray());
}
});
return future;
}
private static ListenableFuture<JSONArray> getCookies(final Context context) {
final SettableFuture<JSONArray> future = SettableFuture.create();
final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context));
accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() {
@Override
public void onSuccess(final List<Account> accounts) {
try {
final JSONArray jsonArray = new JSONArray();
try {
for (final Account cookie : accounts) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookie.getUid());
@ -393,30 +404,29 @@ public final class ExportImportUtils {
jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject);
}
callback.onAccountsJsonLoaded(jsonArray);
return;
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getCookies");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting accounts", e);
}
callback.onAccountsJsonLoaded(new JSONArray());
}
future.set(jsonArray);
}
@Override
public void onDataNotAvailable() {
callback.onAccountsJsonLoaded(new JSONArray());
future.set(new JSONArray());
}
});
return future;
}
@IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true)
@interface ExportImportFlags {}
public interface OnExportStringCreatedCallback {
void onCreated(String exportString);
}
public interface OnAccountJsonLoadedCallback {
void onAccountsJsonLoaded(JSONArray array);
}
public interface OnFavoritesJsonLoadedCallback {
void onFavoritesJsonLoaded(JSONArray array);
}
}
Loading…
Cancel
Save