1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-26 00:27:30 +00:00

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

This commit is contained in:
Ammar Githam 2020-12-08 19:39:39 +09:00
parent 6b8df5fee2
commit cea61eae6c
2 changed files with 172 additions and 158 deletions

View File

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

View File

@ -2,7 +2,6 @@ package awais.instagrabber.utils;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -10,8 +9,14 @@ import android.widget.Toast;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; 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.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -24,7 +29,6 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.datasources.AccountDataSource; 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_FAVORITES = 1 << 1;
public static final int FLAG_SETTINGS = 1 << 2; 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, public static void importData(@NonNull final Context context,
@ExportImportFlags final int flags, @ExportImportFlags final int flags,
@NonNull final File file, @NonNull final File file,
@ -262,128 +224,177 @@ public final class ExportImportUtils {
return false; 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, private static void getExportString(@ExportImportFlags final int flags,
@NonNull final Context context, @NonNull final Context context,
final OnExportStringCreatedCallback callback) { final OnExportStringCreatedCallback callback) {
final Handler innerHandler = new Handler(); if (callback == null) return;
AppExecutors.getInstance().tasksThread().execute(() -> { try {
final CountDownLatch responseWaiter = new CountDownLatch(3); final ImmutableList.Builder<ListenableFuture<?>> futures = ImmutableList.builder();
try { futures.add((flags & FLAG_SETTINGS) == FLAG_SETTINGS
final JSONObject jsonObject = new JSONObject(); ? getSettings(context)
innerHandler.post(() -> { : Futures.immediateFuture(null));
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { futures.add((flags & FLAG_COOKIES) == FLAG_COOKIES
try { ? getCookies(context)
jsonObject.put("settings", getSettings(context)); : Futures.immediateFuture(null));
} catch (JSONException e) { futures.add((flags & FLAG_FAVORITES) == FLAG_FAVORITES
Log.e(TAG, "getExportString: ", e); ? 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();
if (result == null) {
callback.onCreated(jsonObject.toString());
return;
}
try {
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(); try {
}); final JSONArray accounts = (JSONArray) result.get(1);
innerHandler.post(() -> { if (accounts != null) {
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) { jsonObject.put("cookies", accounts);
getCookies(context, array -> { }
try { } catch (Exception e) {
jsonObject.put("cookies", array); Log.e(TAG, "error getting accounts", e);
} catch (JSONException e) {
Log.e(TAG, "error getting accounts", e);
}
responseWaiter.countDown();
});
return;
} }
responseWaiter.countDown(); try {
}); final JSONArray favorites = (JSONArray) result.get(2);
innerHandler.post(() -> { if (favorites != null) {
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) { jsonObject.put("favs", favorites);
getFavorites(context, array -> { }
try { } catch (Exception e) {
jsonObject.put("favs", array); Log.e(TAG, "error getting favorites: ", e);
} catch (JSONException e) {
Log.e(TAG, "getExportString: ", e);
}
responseWaiter.countDown();
});
return;
} }
responseWaiter.countDown(); callback.onCreated(jsonObject.toString());
}); }
responseWaiter.await();
callback.onCreated(jsonObject.toString()); @Override
} catch (final Exception e) { public void onFailure(@NonNull final Throwable t) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); Log.e(TAG, "onFailure: ", t);
if (BuildConfig.DEBUG) Log.e(TAG, "", e); callback.onCreated(null);
} }
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 @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); final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final Map<String, ?> allPrefs = sharedPreferences.getAll(); return AppExecutors.getInstance().tasksThread().submit(() -> {
if (allPrefs == null) { final Map<String, ?> allPrefs = sharedPreferences.getAll();
if (allPrefs == null) {
return new JSONObject();
}
try {
final JSONObject jsonObject = new JSONObject(allPrefs);
jsonObject.remove(Constants.COOKIE);
jsonObject.remove(Constants.DEVICE_UUID);
jsonObject.remove(Constants.PREV_INSTALL_VERSION);
return jsonObject;
} catch (Exception e) {
Log.e(TAG, "Error exporting settings", e);
}
return new JSONObject(); return new JSONObject();
} });
try {
final JSONObject jsonObject = new JSONObject(allPrefs);
jsonObject.remove(Constants.COOKIE);
jsonObject.remove(Constants.DEVICE_UUID);
jsonObject.remove(Constants.PREV_INSTALL_VERSION);
return jsonObject;
} catch (Exception e) {
Log.e(TAG, "Error exporting settings", e);
}
return new JSONObject();
} }
private static void getFavorites(final Context context, final OnFavoritesJsonLoadedCallback callback) { private static ListenableFuture<JSONArray> getFavorites(final Context context) {
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context); final SettableFuture<JSONArray> future = SettableFuture.create();
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource); final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
try { favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() {
favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() { @Override
@Override public void onSuccess(final List<Favorite> favorites) {
public void onSuccess(final List<Favorite> favorites) { final JSONArray jsonArray = new JSONArray();
final JSONArray jsonArray = new JSONArray(); try {
try { for (final Favorite favorite : favorites) {
for (final Favorite favorite : favorites) { final JSONObject jsonObject = new JSONObject();
final JSONObject jsonObject = new JSONObject(); jsonObject.put("q", favorite.getQuery());
jsonObject.put("q", favorite.getQuery()); jsonObject.put("type", favorite.getType().toString());
jsonObject.put("type", favorite.getType().toString()); jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("s", favorite.getDisplayName()); jsonObject.put("pic_url", favorite.getPicUrl());
jsonObject.put("pic_url", favorite.getPicUrl()); jsonObject.put("d", favorite.getDateAdded().getTime());
jsonObject.put("d", favorite.getDateAdded().getTime()); jsonArray.put(jsonObject);
jsonArray.put(jsonObject); }
} } catch (Exception e) {
} catch (Exception e) { if (logCollector != null) {
Log.e(TAG, "onSuccess: Error creating json array", e); logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting favorites", e);
} }
callback.onFavoritesJsonLoaded(jsonArray);
} }
future.set(jsonArray);
}
@Override @Override
public void onDataNotAvailable() { public void onDataNotAvailable() {
callback.onFavoritesJsonLoaded(new JSONArray()); future.set(new JSONArray());
}
});
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
} }
if (BuildConfig.DEBUG) { });
Log.e(TAG, "Error exporting favorites", e); return future;
}
}
} }
private static void getCookies(final Context context, final OnAccountJsonLoadedCallback callback) { private static ListenableFuture<JSONArray> getCookies(final Context context) {
final SettableFuture<JSONArray> future = SettableFuture.create();
final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context));
accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() { accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() {
@Override @Override
public void onSuccess(final List<Account> accounts) { public void onSuccess(final List<Account> accounts) {
final JSONArray jsonArray = new JSONArray();
try { try {
final JSONArray jsonArray = new JSONArray();
for (final Account cookie : accounts) { for (final Account cookie : accounts) {
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookie.getUid()); jsonObject.put("i", cookie.getUid());
@ -393,30 +404,29 @@ public final class ExportImportUtils {
jsonObject.put("profile_pic", cookie.getProfilePic()); jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject); jsonArray.put(jsonObject);
} }
callback.onAccountsJsonLoaded(jsonArray);
return;
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error exporting accounts", 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 @Override
public void onDataNotAvailable() { 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 { public interface OnExportStringCreatedCallback {
void onCreated(String exportString); void onCreated(String exportString);
} }
public interface OnAccountJsonLoadedCallback {
void onAccountsJsonLoaded(JSONArray array);
}
public interface OnFavoritesJsonLoadedCallback {
void onFavoritesJsonLoaded(JSONArray array);
}
} }