From f94694fff309f6a66f1b9f68bf0370269d9fa339 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 6 Dec 2020 05:17:46 +0900 Subject: [PATCH 1/5] Update/add dependencies and enable proguard (no obfuscation). --- app/build.gradle | 21 ++++++++++++++----- app/proguard-rules.pro | 7 +++++++ .../res/layout/layout_profile_details.xml | 2 +- build.gradle | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a803818e..ccc82cfb 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,8 +32,13 @@ android { aaptOptions { additionalParameters '--no-version-vectors' } buildTypes { + debug { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -45,19 +50,19 @@ configurations.all { } dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' def appcompat_version = "1.2.0" - def nav_version = '2.3.1' + def nav_version = '2.3.2' - implementation 'com.google.android.material:material:1.3.0-alpha03' + implementation 'com.google.android.material:material:1.3.0-alpha04' implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0' implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0' implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat-resources:$appcompat_version" - implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06" + implementation "androidx.recyclerview:recyclerview:1.2.0-beta01" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.navigation:navigation-fragment:$nav_version" @@ -69,6 +74,12 @@ dependencies { implementation 'com.google.guava:guava:27.0.1-android' + // Room + def room_version = "2.2.5" + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-guava:$room_version" + annotationProcessor "androidx.room:room-compiler:$room_version" + // implementation 'com.github.hendrawd:StorageUtil:1.1.0' implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b42451..aeb70430 100755 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,3 +19,10 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + +#noinspection ShrinkerUnresolvedReference +#-keep class !com.google.android.exoplayer2.**, ** { *; } + +#-keep class !awais.instagrabber.** { *; } + +-dontobfuscate \ No newline at end of file diff --git a/app/src/main/res/layout/layout_profile_details.xml b/app/src/main/res/layout/layout_profile_details.xml index 0c46eab4..41abd3b3 100644 --- a/app/src/main/res/layout/layout_profile_details.xml +++ b/app/src/main/res/layout/layout_profile_details.xml @@ -101,7 +101,7 @@ Date: Mon, 7 Dec 2020 19:47:03 +0900 Subject: [PATCH 2/5] Preparing migration to Room --- app/src/main/AndroidManifest.xml | 3 +- .../instagrabber/InstaGrabberApplication.java | 7 - .../activities/DirectDownload.java | 3 + .../adapters/AccountSwitcherAdapter.java | 18 +- .../adapters/FavoritesAdapter.java | 18 +- .../viewholder/FavoriteViewHolder.java | 4 +- .../asyncs/NotificationsFetcher.java | 4 - .../instagrabber/asyncs/PostFetcher.java | 3 - .../db/datasources/AccountDataSource.java | 182 +++++++ .../db/datasources/FavoriteDataSource.java | 180 +++++++ .../instagrabber/db/entities/Account.java | 114 ++++ .../instagrabber/db/entities/Favorite.java | 103 ++++ .../db/repositories/AccountRepository.java | 131 +++++ .../db/repositories/FavoriteRepository.java | 92 ++++ .../db/repositories/RepositoryCallback.java | 11 + .../AccountSwitcherDialogFragment.java | 59 ++- .../dialogs/RestoreBackupDialogFragment.java | 5 +- .../fragments/FavoritesFragment.java | 158 ++++-- .../fragments/HashTagFragment.java | 95 ++-- .../fragments/LocationFragment.java | 98 ++-- .../NotificationsViewerFragment.java | 6 +- .../fragments/main/ProfileFragment.java | 149 ++++-- .../settings/MorePreferencesFragment.java | 139 +++-- .../instagrabber/utils/AppExecutors.java | 87 +++ .../awais/instagrabber/utils/CookieUtils.java | 25 +- .../awais/instagrabber/utils/DataBox.java | 498 ++---------------- .../instagrabber/utils/ExportImportUtils.java | 343 +++++++----- .../java/awais/instagrabber/utils/Utils.java | 1 - .../viewmodels/FavoritesViewModel.java | 6 +- app/src/main/res/layout/activity_login.xml | 9 +- 30 files changed, 1701 insertions(+), 850 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java create mode 100644 app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java create mode 100644 app/src/main/java/awais/instagrabber/db/entities/Account.java create mode 100644 app/src/main/java/awais/instagrabber/db/entities/Favorite.java create mode 100644 app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java create mode 100644 app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java create mode 100644 app/src/main/java/awais/instagrabber/db/repositories/RepositoryCallback.java create mode 100644 app/src/main/java/awais/instagrabber/utils/AppExecutors.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3d33cda0..556de192 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,7 +108,8 @@ + android:parentActivityName=".activities.MainActivity" + android:theme="@style/AppTheme.Light.White"> diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index 5f87ed0e..874b425f 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java @@ -13,7 +13,6 @@ import java.text.SimpleDateFormat; import java.util.UUID; import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.SettingsHelper; import awaisomereport.CrashReporter; @@ -21,7 +20,6 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER; import static awais.instagrabber.utils.Utils.clipboardManager; -import static awais.instagrabber.utils.Utils.dataBox; import static awais.instagrabber.utils.Utils.datetimeParser; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -58,11 +56,6 @@ public final class InstaGrabberApplication extends Application { CookieHandler.setDefault(NET_COOKIE_MANAGER); - final Context appContext = getApplicationContext(); - - if (dataBox == null) - dataBox = DataBox.getInstance(appContext); - if (settingsHelper == null) settingsHelper = new SettingsHelper(this); diff --git a/app/src/main/java/awais/instagrabber/activities/DirectDownload.java b/app/src/main/java/awais/instagrabber/activities/DirectDownload.java index 4d675b3e..448aab78 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectDownload.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectDownload.java @@ -26,9 +26,11 @@ import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.IntentModel; import awais.instagrabber.models.enums.IntentModelType; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.IntentUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; public final class DirectDownload extends AppCompatActivity { private static final int NOTIFICATION_ID = 1900000000; @@ -88,6 +90,7 @@ public final class DirectDownload extends AppCompatActivity { } private synchronized void doDownload() { + CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); notificationManager = NotificationManagerCompat.from(getApplicationContext()); final Intent intent = getIntent(); final String action = intent.getAction(); diff --git a/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java b/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java index 8e984ddc..10cd73dc 100644 --- a/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/AccountSwitcherAdapter.java @@ -14,21 +14,21 @@ import androidx.recyclerview.widget.RecyclerView; import awais.instagrabber.R; import awais.instagrabber.databinding.PrefAccountSwitcherBinding; +import awais.instagrabber.db.entities.Account; import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DataBox; import static awais.instagrabber.utils.Utils.settingsHelper; -public class AccountSwitcherAdapter extends ListAdapter { +public class AccountSwitcherAdapter extends ListAdapter { private static final String TAG = "AccountSwitcherAdapter"; - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { @Override - public boolean areItemsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) { + public boolean areItemsTheSame(@NonNull final Account oldItem, @NonNull final Account newItem) { return oldItem.getUid().equals(newItem.getUid()); } @Override - public boolean areContentsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) { + public boolean areContentsTheSame(@NonNull final Account oldItem, @NonNull final Account newItem) { return oldItem.getUid().equals(newItem.getUid()); } }; @@ -53,7 +53,7 @@ public class AccountSwitcherAdapter extends ListAdapter { @@ -102,7 +102,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter list) { + public void submitList(@Nullable final List list) { if (list == null) { differ.submitList(null); return; @@ -110,7 +110,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter list, @Nullable final Runnable commitCallback) { + public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { if (list == null) { differ.submitList(null, commitCallback); return; @@ -119,8 +119,8 @@ public class FavoritesAdapter extends RecyclerView.Adapter sectionAndSort(@NonNull final List list) { - final List listCopy = new ArrayList<>(list); + private List sectionAndSort(@NonNull final List list) { + final List listCopy = new ArrayList<>(list); Collections.sort(listCopy, (o1, o2) -> { if (o1.getType() == o2.getType()) return 0; // keep users at top @@ -133,7 +133,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter modelOrHeaders = new ArrayList<>(); for (int i = 0; i < listCopy.size(); i++) { - final DataBox.FavoriteModel model = listCopy.get(i); + final Favorite model = listCopy.get(i); final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1); boolean prevWasSameType = prev != null && prev.model.getType() == model.getType(); if (prevWasSameType) { @@ -156,7 +156,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter> { private static final String TAG = "NotificationsFetcher"; @@ -37,8 +35,6 @@ public final class NotificationsFetcher extends AsyncTask doInBackground(final Void... voids) { List result = new ArrayList<>(); final String url = "https://www.instagram.com/accounts/activity/?__a=1"; - CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); - try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setInstanceFollowRedirects(false); diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java index 39ba0d9e..f87e5103 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java @@ -17,11 +17,9 @@ import awais.instagrabber.models.PostChild; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.NetworkUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; @@ -39,7 +37,6 @@ public final class PostFetcher extends AsyncTask { @Override protected FeedModel doInBackground(final Void... voids) { - CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection(); diff --git a/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java new file mode 100644 index 00000000..2aaa2f8a --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java @@ -0,0 +1,182 @@ +package awais.instagrabber.db.datasources; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.utils.DataBox; +import awais.instagrabber.utils.TextUtils; + +import static awais.instagrabber.utils.DataBox.KEY_COOKIE; +import static awais.instagrabber.utils.DataBox.KEY_FULL_NAME; +import static awais.instagrabber.utils.DataBox.KEY_ID; +import static awais.instagrabber.utils.DataBox.KEY_PROFILE_PIC; +import static awais.instagrabber.utils.DataBox.KEY_UID; +import static awais.instagrabber.utils.DataBox.KEY_USERNAME; +import static awais.instagrabber.utils.DataBox.TABLE_COOKIES; + +public class AccountDataSource { + private static final String TAG = AccountDataSource.class.getSimpleName(); + + private static AccountDataSource INSTANCE; + + private final DataBox dataBox; + + private AccountDataSource(@NonNull Context context) { + dataBox = DataBox.getInstance(context); + } + + public static synchronized AccountDataSource getInstance(@NonNull Context context) { + if (INSTANCE == null) { + INSTANCE = new AccountDataSource(context); + } + return INSTANCE; + } + + @Nullable + public final Account getAccount(final String uid) { + Account cookie = null; + try (final SQLiteDatabase db = dataBox.getReadableDatabase(); + final Cursor cursor = db.query(TABLE_COOKIES, + new String[]{ + KEY_ID, + KEY_UID, + KEY_USERNAME, + KEY_COOKIE, + KEY_FULL_NAME, + KEY_PROFILE_PIC + }, + KEY_UID + "=?", + new String[]{uid}, + null, + null, + null)) { + if (cursor != null && cursor.moveToFirst()) + cookie = new Account( + cursor.getInt(cursor.getColumnIndex(KEY_ID)), + cursor.getString(cursor.getColumnIndex(KEY_UID)), + cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), + cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), + cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), + cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) + ); + } + return cookie; + } + + @NonNull + public final List getAllAccounts() { + final List cookies = new ArrayList<>(); + try (final SQLiteDatabase db = dataBox.getReadableDatabase(); + final Cursor cursor = db.query(TABLE_COOKIES, + new String[]{ + KEY_ID, + KEY_UID, + KEY_USERNAME, + KEY_COOKIE, + KEY_FULL_NAME, + KEY_PROFILE_PIC + }, + null, + null, + null, + null, + null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + cookies.add(new Account( + cursor.getInt(cursor.getColumnIndex(KEY_ID)), + cursor.getString(cursor.getColumnIndex(KEY_UID)), + cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), + cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), + cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), + cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) + )); + } while (cursor.moveToNext()); + } + } + return cookies; + } + + // public final void insertOrUpdateAccount(@NonNull final Account account) { + // insertOrUpdateAccount( + // account.getUid(), + // account.getUsername(), + // account.getCookie(), + // account.getFullName(), + // account.getProfilePic() + // ); + // } + + public final void insertOrUpdateAccount(final String uid, + final String username, + final String cookie, + final String fullName, + final String profilePicUrl) { + if (TextUtils.isEmpty(uid)) return; + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + db.beginTransaction(); + try { + final ContentValues values = new ContentValues(); + values.put(KEY_USERNAME, username); + values.put(KEY_COOKIE, cookie); + values.put(KEY_UID, uid); + values.put(KEY_FULL_NAME, fullName); + values.put(KEY_PROFILE_PIC, profilePicUrl); + final int rows = db.update(TABLE_COOKIES, values, KEY_UID + "=?", new String[]{uid}); + if (rows != 1) { + db.insert(TABLE_COOKIES, null, values); + } + db.setTransactionSuccessful(); + } catch (final Exception e) { + if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); + } finally { + db.endTransaction(); + } + } + } + + public final synchronized void deleteAccount(@NonNull final Account account) { + final String cookieModelUid = account.getUid(); + if (!TextUtils.isEmpty(cookieModelUid)) { + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + db.beginTransaction(); + try { + final int rowsDeleted = db.delete(TABLE_COOKIES, KEY_UID + "=? AND " + KEY_USERNAME + "=? AND " + KEY_COOKIE + "=?", + new String[]{cookieModelUid, account.getUsername(), account.getCookie()}); + + if (rowsDeleted > 0) db.setTransactionSuccessful(); + } catch (final Exception e) { + if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + } finally { + db.endTransaction(); + } + } + } + } + + public final synchronized void deleteAllAccounts() { + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + db.beginTransaction(); + try { + final int rowsDeleted = db.delete(TABLE_COOKIES, null, null); + + if (rowsDeleted > 0) db.setTransactionSuccessful(); + } catch (final Exception e) { + if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + } finally { + db.endTransaction(); + } + } + } +} diff --git a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java new file mode 100644 index 00000000..92a566c6 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java @@ -0,0 +1,180 @@ +package awais.instagrabber.db.datasources; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.DataBox; +import awais.instagrabber.utils.TextUtils; +import awaisomereport.LogCollector; + +import static awais.instagrabber.utils.DataBox.FAV_COL_DATE_ADDED; +import static awais.instagrabber.utils.DataBox.FAV_COL_DISPLAY_NAME; +import static awais.instagrabber.utils.DataBox.FAV_COL_ID; +import static awais.instagrabber.utils.DataBox.FAV_COL_PIC_URL; +import static awais.instagrabber.utils.DataBox.FAV_COL_QUERY; +import static awais.instagrabber.utils.DataBox.FAV_COL_TYPE; +import static awais.instagrabber.utils.DataBox.TABLE_FAVORITES; +import static awais.instagrabber.utils.Utils.logCollector; + +public class FavoriteDataSource { + private static final String TAG = FavoriteDataSource.class.getSimpleName(); + + private static FavoriteDataSource INSTANCE; + + private final DataBox dataBox; + + private FavoriteDataSource(@NonNull Context context) { + dataBox = DataBox.getInstance(context); + } + + public static synchronized FavoriteDataSource getInstance(@NonNull Context context) { + if (INSTANCE == null) { + INSTANCE = new FavoriteDataSource(context); + } + return INSTANCE; + } + + @Nullable + public final Favorite getFavorite(@NonNull final String query, @NonNull final FavoriteType type) { + try (final SQLiteDatabase db = dataBox.getReadableDatabase(); + final Cursor cursor = db.query(TABLE_FAVORITES, + new String[]{ + FAV_COL_ID, + FAV_COL_QUERY, + FAV_COL_TYPE, + FAV_COL_DISPLAY_NAME, + FAV_COL_PIC_URL, + FAV_COL_DATE_ADDED + }, + FAV_COL_QUERY + "=?" + " AND " + FAV_COL_TYPE + "=?", + new String[]{ + query, + type.toString() + }, + null, + null, + null)) { + if (cursor != null && cursor.moveToFirst()) { + FavoriteType favoriteType = null; + try { + favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} + return new Favorite( + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + favoriteType, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) + ); + } + } + return null; + } + + @NonNull + public final List getAllFavorites() { + final List favorites = new ArrayList<>(); + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + try (final Cursor cursor = db.query(TABLE_FAVORITES, + new String[]{ + FAV_COL_ID, + FAV_COL_QUERY, + FAV_COL_TYPE, + FAV_COL_DISPLAY_NAME, + FAV_COL_PIC_URL, + FAV_COL_DATE_ADDED + }, + null, + null, + null, + null, + null)) { + if (cursor != null && cursor.moveToFirst()) { + db.beginTransaction(); + Favorite tempFav; + do { + FavoriteType type = null; + try { + type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} + tempFav = new Favorite( + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + type, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) + ); + favorites.add(tempFav); + } while (cursor.moveToNext()); + db.endTransaction(); + } + } catch (final Exception e) { + Log.e(TAG, "", e); + } + } + return favorites; + } + + public final synchronized Favorite insertOrUpdateFavorite(@NonNull final Favorite model) { + final String query = model.getQuery(); + if (!TextUtils.isEmpty(query)) { + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + db.beginTransaction(); + try { + dataBox.insertOrUpdateFavorite(db, model); + db.setTransactionSuccessful(); + } catch (Exception e) { + if (logCollector != null) { + logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "insertOrUpdateFavorite"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error adding/updating favorite", e); + } + } finally { + db.endTransaction(); + } + } + return getFavorite(model.getQuery(), model.getType()); + } + return null; + } + + public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { + if (!TextUtils.isEmpty(query)) { + try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { + db.beginTransaction(); + try { + final int rowsDeleted = db.delete(TABLE_FAVORITES, + FAV_COL_QUERY + "=?" + + " AND " + FAV_COL_TYPE + "=?", + new String[]{query, type.toString()}); + + if (rowsDeleted > 0) db.setTransactionSuccessful(); + } catch (final Exception e) { + if (logCollector != null) { + logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error", e); + } + } finally { + db.endTransaction(); + } + } + } + } +} diff --git a/app/src/main/java/awais/instagrabber/db/entities/Account.java b/app/src/main/java/awais/instagrabber/db/entities/Account.java new file mode 100644 index 00000000..7f849c6f --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/entities/Account.java @@ -0,0 +1,114 @@ +package awais.instagrabber.db.entities; + +import androidx.annotation.NonNull; +import androidx.core.util.ObjectsCompat; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import awais.instagrabber.utils.TextUtils; + +@Entity(tableName = "cookies") +public class Account { + + @PrimaryKey + @ColumnInfo(name = "id") + private final int id; + + @ColumnInfo(name = "uid") + private final String uid; + + @ColumnInfo(name = "username") + private final String username; + + @ColumnInfo(name = "cookie") + private final String cookie; + + @ColumnInfo(name = "full_name") + private final String fullName; + + @ColumnInfo(name = "profile_pic") + private final String profilePic; + + private boolean selected; + + public Account(final int id, + final String uid, + final String username, + final String cookie, + final String fullName, + final String profilePic) { + this.id = id; + this.uid = uid; + this.username = username; + this.cookie = cookie; + this.fullName = fullName; + this.profilePic = profilePic; + } + + public int getId() { + return id; + } + + public String getUid() { + return uid; + } + + public String getUsername() { + return username; + } + + public String getCookie() { + return cookie; + } + + public String getFullName() { + return fullName; + } + + public String getProfilePic() { + return profilePic; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(final boolean selected) { + this.selected = selected; + } + + public boolean isValid() { + return !TextUtils.isEmpty(uid) + && !TextUtils.isEmpty(username) + && !TextUtils.isEmpty(cookie); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Account that = (Account) o; + return ObjectsCompat.equals(uid, that.uid) && + ObjectsCompat.equals(username, that.username) && + ObjectsCompat.equals(cookie, that.cookie); + } + + @Override + public int hashCode() { + return ObjectsCompat.hash(uid, username, cookie); + } + + @NonNull + @Override + public String toString() { + return "Account{" + + "uid='" + uid + '\'' + + ", username='" + username + '\'' + + ", cookie='" + cookie + '\'' + + ", fullName='" + fullName + '\'' + + ", profilePic='" + profilePic + '\'' + + ", selected=" + selected + + '}'; + } +} diff --git a/app/src/main/java/awais/instagrabber/db/entities/Favorite.java b/app/src/main/java/awais/instagrabber/db/entities/Favorite.java new file mode 100644 index 00000000..7e3bada4 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/entities/Favorite.java @@ -0,0 +1,103 @@ +package awais.instagrabber.db.entities; + +import androidx.annotation.NonNull; +import androidx.core.util.ObjectsCompat; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import java.util.Date; + +import awais.instagrabber.models.enums.FavoriteType; + +@Entity(tableName = "favorites") +public class Favorite { + + @PrimaryKey + @ColumnInfo(name = "id") + private final int id; + + @ColumnInfo(name = "query_text") + private final String query; + + @ColumnInfo(name = "type") + private final FavoriteType type; + + @ColumnInfo(name = "display_name") + private final String displayName; + + @ColumnInfo(name = "pic_url") + private final String picUrl; + + @ColumnInfo(name = "date_added") + private final Date dateAdded; + + public Favorite(final int id, + final String query, + final FavoriteType type, + final String displayName, + final String picUrl, + final Date dateAdded) { + this.id = id; + this.query = query; + this.type = type; + this.displayName = displayName; + this.picUrl = picUrl; + this.dateAdded = dateAdded; + } + + public int getId() { + return id; + } + + public String getQuery() { + return query; + } + + public FavoriteType getType() { + return type; + } + + public String getDisplayName() { + return displayName; + } + + public String getPicUrl() { + return picUrl; + } + + public Date getDateAdded() { + return dateAdded; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Favorite that = (Favorite) o; + return id == that.id && + ObjectsCompat.equals(query, that.query) && + type == that.type && + ObjectsCompat.equals(displayName, that.displayName) && + ObjectsCompat.equals(picUrl, that.picUrl) && + ObjectsCompat.equals(dateAdded, that.dateAdded); + } + + @Override + public int hashCode() { + return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded); + } + + @NonNull + @Override + public String toString() { + return "FavoriteModel{" + + "id=" + id + + ", query='" + query + '\'' + + ", type=" + type + + ", displayName='" + displayName + '\'' + + ", picUrl='" + picUrl + '\'' + + ", dateAdded=" + dateAdded + + '}'; + } +} diff --git a/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java new file mode 100644 index 00000000..529d848d --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java @@ -0,0 +1,131 @@ +package awais.instagrabber.db.repositories; + +import java.util.List; + +import awais.instagrabber.db.datasources.AccountDataSource; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.utils.AppExecutors; + +public class AccountRepository { + private static final String TAG = AccountRepository.class.getSimpleName(); + + private static AccountRepository instance; + + private final AppExecutors appExecutors; + private final AccountDataSource accountDataSource; + + // private List cachedAccounts; + + private AccountRepository(final AppExecutors appExecutors, final AccountDataSource accountDataSource) { + this.appExecutors = appExecutors; + this.accountDataSource = accountDataSource; + } + + public static AccountRepository getInstance(final AppExecutors appExecutors, final AccountDataSource accountDataSource) { + if (instance == null) { + instance = new AccountRepository(appExecutors, accountDataSource); + } + return instance; + } + + public void getAccount(final String uid, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final Account account = accountDataSource.getAccount(uid); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (account == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(account); + }); + }); + } + + public void getAllAccounts(final RepositoryCallback> callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final List accounts = accountDataSource.getAllAccounts(); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (accounts == null) { + callback.onDataNotAvailable(); + return; + } + // cachedAccounts = accounts; + callback.onSuccess(accounts); + }); + }); + } + + public void insertOrUpdateAccounts(final List accounts, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + for (final Account account : accounts) { + accountDataSource.insertOrUpdateAccount(account.getUid(), + account.getUsername(), + account.getCookie(), + account.getFullName(), + account.getProfilePic()); + } + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } + + public void insertOrUpdateAccount(final String uid, + final String username, + final String cookie, + final String fullName, + final String profilePicUrl, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + accountDataSource.insertOrUpdateAccount(uid, username, cookie, fullName, profilePicUrl); + final Account updated = accountDataSource.getAccount(uid); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (updated == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(updated); + }); + }); + } + + public void deleteAccount(final Account account, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + accountDataSource.deleteAccount(account); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } + + public void deleteAllAccounts(final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + accountDataSource.deleteAllAccounts(); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } + +} diff --git a/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java new file mode 100644 index 00000000..3f942b6b --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java @@ -0,0 +1,92 @@ +package awais.instagrabber.db.repositories; + +import java.util.List; + +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.AppExecutors; + +public class FavoriteRepository { + private static final String TAG = FavoriteRepository.class.getSimpleName(); + + private static FavoriteRepository instance; + + private final AppExecutors appExecutors; + private final FavoriteDataSource favoriteDataSource; + + private FavoriteRepository(final AppExecutors appExecutors, final FavoriteDataSource favoriteDataSource) { + this.appExecutors = appExecutors; + this.favoriteDataSource = favoriteDataSource; + } + + public static FavoriteRepository getInstance(final AppExecutors appExecutors, final FavoriteDataSource favoriteDataSource) { + if (instance == null) { + instance = new FavoriteRepository(appExecutors, favoriteDataSource); + } + return instance; + } + + public void getFavorite(final String query, final FavoriteType type, final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final Favorite favorite = favoriteDataSource.getFavorite(query, type); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (favorite == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(favorite); + }); + }); + } + + public void getAllFavorites(final RepositoryCallback> callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final List favorites = favoriteDataSource.getAllFavorites(); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (favorites == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(favorites); + }); + }); + } + + public void insertOrUpdateFavorite(final Favorite favorite, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final Favorite updated = favoriteDataSource.insertOrUpdateFavorite(favorite); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (updated == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(updated); + }); + }); + } + + public void deleteFavorite(final String query, + final FavoriteType type, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + favoriteDataSource.deleteFavorite(query, type); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/db/repositories/RepositoryCallback.java b/app/src/main/java/awais/instagrabber/db/repositories/RepositoryCallback.java new file mode 100644 index 00000000..6663239c --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/repositories/RepositoryCallback.java @@ -0,0 +1,11 @@ +package awais.instagrabber.db.repositories; + +import androidx.annotation.MainThread; + +public interface RepositoryCallback { + @MainThread + void onSuccess(T result); + + @MainThread + void onDataNotAvailable(); +} diff --git a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java index e6de23b3..d1830243 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java @@ -21,9 +21,13 @@ import java.util.List; import awais.instagrabber.R; import awais.instagrabber.adapters.AccountSwitcherAdapter; import awais.instagrabber.databinding.DialogAccountSwitcherBinding; +import awais.instagrabber.db.datasources.AccountDataSource; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.db.repositories.AccountRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -31,9 +35,20 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class AccountSwitcherDialogFragment extends DialogFragment { - private final OnAddAccountClickListener onAddAccountClickListener; + private AccountRepository accountRepository; + + private OnAddAccountClickListener onAddAccountClickListener; private DialogAccountSwitcherBinding binding; + public AccountSwitcherDialogFragment() { + accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + } + + public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) { + this.onAddAccountClickListener = onAddAccountClickListener; + accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + } + private final AccountSwitcherAdapter.OnAccountClickListener accountClickListener = (model, isCurrent) -> { if (isCurrent) { dismiss(); @@ -59,8 +74,18 @@ public class AccountSwitcherDialogFragment extends DialogFragment { new AlertDialog.Builder(context) .setMessage(getString(R.string.quick_access_confirm_delete, model.getUsername())) .setPositiveButton(R.string.yes, (dialog, which) -> { - Utils.dataBox.delUserCookie(model); - dismiss(); + if (accountRepository == null) return; + accountRepository.deleteAccount(model, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + dismiss(); + } + + @Override + public void onDataNotAvailable() { + dismiss(); + } + }); }) .setNegativeButton(R.string.cancel, null) .show(); @@ -68,10 +93,6 @@ public class AccountSwitcherDialogFragment extends DialogFragment { return true; }; - public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) { - this.onAddAccountClickListener = onAddAccountClickListener; - } - @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, @@ -102,11 +123,19 @@ public class AccountSwitcherDialogFragment extends DialogFragment { private void init() { final AccountSwitcherAdapter adapter = new AccountSwitcherAdapter(accountClickListener, accountLongClickListener); binding.accounts.setAdapter(adapter); - final List allUsers = Utils.dataBox.getAllCookies(); - if (allUsers == null) return; - final String cookie = settingsHelper.getString(Constants.COOKIE); - sortUserList(cookie, allUsers); - adapter.submitList(allUsers); + if (accountRepository == null) return; + accountRepository.getAllAccounts(new RepositoryCallback>() { + @Override + public void onSuccess(final List accounts) { + if (accounts == null) return; + final String cookie = settingsHelper.getString(Constants.COOKIE); + sortUserList(cookie, accounts); + adapter.submitList(accounts); + } + + @Override + public void onDataNotAvailable() {} + }); binding.addAccountBtn.setOnClickListener(v -> { if (onAddAccountClickListener == null) return; onAddAccountClickListener.onAddAccountClick(this); @@ -125,9 +154,9 @@ public class AccountSwitcherDialogFragment extends DialogFragment { * @param cookie active cookie * @param allUsers list of users */ - private void sortUserList(final String cookie, final List allUsers) { + private void sortUserList(final String cookie, final List allUsers) { boolean sortByName = true; - for (final DataBox.CookieModel user : allUsers) { + for (final Account user : allUsers) { if (TextUtils.isEmpty(user.getFullName())) { sortByName = false; break; diff --git a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java index 9b9de1e2..7967b9e2 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java @@ -4,6 +4,7 @@ import android.app.Dialog; import android.content.Context; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.Handler; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -93,7 +94,7 @@ public class RestoreBackupDialogFragment extends DialogFragment { return; } binding.btnRestore.setEnabled(false); - binding.btnRestore.setOnClickListener(v -> { + binding.btnRestore.setOnClickListener(v -> new Handler().post(() -> { int flags = 0; if (binding.cbFavorites.isChecked()) { flags |= ExportImportUtils.FLAG_FAVORITES; @@ -122,7 +123,7 @@ public class RestoreBackupDialogFragment extends DialogFragment { } catch (IncorrectPasswordException e) { binding.passwordField.setError("Incorrect password"); } - }); + })); binding.etPassword.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {} diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java index 3d344a59..8631add3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java @@ -28,9 +28,12 @@ import awais.instagrabber.adapters.FavoritesAdapter; import awais.instagrabber.asyncs.LocationFetcher; import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.databinding.FragmentFavoritesBinding; -import awais.instagrabber.utils.DataBox; +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.FavoritesViewModel; public class FavoritesFragment extends Fragment { @@ -41,6 +44,13 @@ public class FavoritesFragment extends Fragment { private RecyclerView root; private FavoritesViewModel favoritesViewModel; private FavoritesAdapter adapter; + private FavoriteRepository favoriteRepository; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext())); + } @NonNull @Override @@ -68,9 +78,16 @@ public class FavoritesFragment extends Fragment { if (favoritesViewModel == null || adapter == null) return; // refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); - final List allFavorites = Utils.dataBox.getAllFavorites(); - favoritesViewModel.getList().postValue(allFavorites); - fetchMissingInfo(allFavorites); + favoriteRepository.getAllFavorites(new RepositoryCallback>() { + @Override + public void onSuccess(final List favorites) { + favoritesViewModel.getList().postValue(favorites); + fetchMissingInfo(favorites); + } + + @Override + public void onDataNotAvailable() {} + }); } private void init() { @@ -114,30 +131,43 @@ public class FavoritesFragment extends Fragment { if (context == null) return false; new MaterialAlertDialogBuilder(context) .setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery())) - .setPositiveButton(R.string.yes, (d, which) -> { - Utils.dataBox.deleteFavorite(model.getQuery(), model.getType()); - d.dismiss(); - favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites()); - }) + .setPositiveButton(R.string.yes, (d, which) -> favoriteRepository + .deleteFavorite(model.getQuery(), model.getType(), new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + d.dismiss(); + favoriteRepository.getAllFavorites(new RepositoryCallback>() { + @Override + public void onSuccess(final List result) { + favoritesViewModel.getList().postValue(result); + } + + @Override + public void onDataNotAvailable() {} + }); + } + + @Override + public void onDataNotAvailable() {} + })) .setNegativeButton(R.string.no, null) .show(); return true; }); binding.favoriteList.setAdapter(adapter); - // favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); } - private void fetchMissingInfo(final List allFavorites) { + private void fetchMissingInfo(final List allFavorites) { final Runnable runnable = () -> { - final List updatedList = new ArrayList<>(allFavorites); + final List updatedList = new ArrayList<>(allFavorites); // cyclic barrier is to make the async calls synchronous final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { // Log.d(TAG, "fetchMissingInfo: barrier action"); favoritesViewModel.getList().postValue(new ArrayList<>(updatedList)); }); try { - for (final DataBox.FavoriteModel model : allFavorites) { + for (final Favorite model : allFavorites) { cyclicBarrier.reset(); // if the model has missing pic or display name (for user and location), fetch those details switch (model.getType()) { @@ -145,27 +175,37 @@ public class FavoritesFragment extends Fragment { if (TextUtils.isEmpty(model.getDisplayName()) || TextUtils.isEmpty(model.getPicUrl())) { new LocationFetcher(model.getQuery(), result -> { - try { - if (result == null) return; - final int i = updatedList.indexOf(model); - updatedList.remove(i); - final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( - model.getId(), - model.getQuery(), - model.getType(), - result.getName(), - result.getSdProfilePic(), - model.getDateAdded() - ); - Utils.dataBox.addOrUpdateFavorite(updated); - updatedList.add(i, updated); - } finally { - try { - cyclicBarrier.await(); - } catch (BrokenBarrierException | InterruptedException e) { - Log.e(TAG, "fetchMissingInfo: ", e); + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final Favorite updated = new Favorite( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + updatedList.add(i, updated); + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } } - } + + @Override + public void onDataNotAvailable() { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }); }).execute(); cyclicBarrier.await(); } @@ -174,27 +214,37 @@ public class FavoritesFragment extends Fragment { if (TextUtils.isEmpty(model.getDisplayName()) || TextUtils.isEmpty(model.getPicUrl())) { new ProfileFetcher(model.getQuery(), result -> { - try { - if (result == null) return; - final int i = updatedList.indexOf(model); - updatedList.remove(i); - final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( - model.getId(), - model.getQuery(), - model.getType(), - result.getName(), - result.getSdProfilePic(), - model.getDateAdded() - ); - Utils.dataBox.addOrUpdateFavorite(updated); - updatedList.add(i, updated); - } finally { - try { - cyclicBarrier.await(); - } catch (BrokenBarrierException | InterruptedException e) { - Log.e(TAG, "fetchMissingInfo: ", e); + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final Favorite updated = new Favorite( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } } - } + + @Override + public void onDataNotAvailable() { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }); + updatedList.add(i, updated); }).execute(); cyclicBarrier.await(); } diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 593e5750..3400d4f6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -49,15 +49,19 @@ import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentHashtagBinding; import awais.instagrabber.databinding.LayoutHashtagDetailsBinding; +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -454,40 +458,61 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe } else { hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE); } - final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); - final boolean isFav = favorite != null; hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE); - hashtagDetailsBinding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 - : R.drawable.ic_outline_star_plus_24); - hashtagDetailsBinding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); - hashtagDetailsBinding.favChip.setOnClickListener(v -> { - final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); - final boolean isFavorite = fav != null; - final String message; - if (isFavorite) { - Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG); - hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites); - hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); - message = getString(R.string.removed_from_favs); - } else { - Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel( - -1, - hashtag.substring(1), - FavoriteType.HASHTAG, - hashtagModel.getName(), - null, - new Date() - )); - hashtagDetailsBinding.favChip.setText(R.string.favorite_short); + final FavoriteRepository favoriteRepository = FavoriteRepository + .getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext())); + favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); - message = getString(R.string.added_to_favs); + hashtagDetailsBinding.favChip.setText(R.string.favorite_short); + } + + @Override + public void onDataNotAvailable() { + hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites); } - final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); - snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) - .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) - .setAnchorView(fragmentActivity.getBottomNavView()) - .show(); }); + hashtagDetailsBinding.favChip.setOnClickListener( + v -> favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + favoriteRepository.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites); + hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + showSnackbar(getString(R.string.removed_from_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + + @Override + public void onDataNotAvailable() { + favoriteRepository.insertOrUpdateFavorite(new Favorite( + -1, + hashtag.substring(1), + FavoriteType.HASHTAG, + hashtagModel.getName(), + null, + new Date() + ), new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + hashtagDetailsBinding.favChip.setText(R.string.favorite_short); + hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + showSnackbar(getString(R.string.added_to_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + })); hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); final String postCount = String.valueOf(hashtagModel.getPostCount()); final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); @@ -504,6 +529,14 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe }); } + private void showSnackbar(final String message) { + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + } + private void fetchStories() { if (!isLoggedIn) return; storiesFetching = true; diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index c66543fc..0ddde3fb 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -52,15 +52,19 @@ import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentLocationBinding; import awais.instagrabber.databinding.LayoutLocationDetailsBinding; +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -443,39 +447,63 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR locationDetailsBinding.locationUrl.setVisibility(View.VISIBLE); locationDetailsBinding.locationUrl.setText(TextUtils.getSpannableUrl(url)); } - final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); - final boolean isFav = favorite != null; + final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(getContext()); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource); locationDetailsBinding.favChip.setVisibility(View.VISIBLE); - locationDetailsBinding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 - : R.drawable.ic_outline_star_plus_24); - locationDetailsBinding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); - locationDetailsBinding.favChip.setOnClickListener(v -> { - final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); - final boolean isFavorite = fav != null; - final String message; - if (isFavorite) { - Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION); - locationDetailsBinding.favChip.setText(R.string.add_to_favorites); - locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); - message = getString(R.string.removed_from_favs); - } else { - Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel( - -1, - locationId, - FavoriteType.LOCATION, - locationModel.getName(), - locationModel.getSdProfilePic(), - new Date() - )); - locationDetailsBinding.favChip.setText(R.string.favorite_short); + favoriteRepository.getFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); - message = getString(R.string.added_to_favs); + locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + locationDetailsBinding.favChip.setText(R.string.favorite_short); } - final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); - snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) - .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) - .setAnchorView(fragmentActivity.getBottomNavView()) - .show(); + + @Override + public void onDataNotAvailable() { + locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + locationDetailsBinding.favChip.setText(R.string.add_to_favorites); + } + }); + locationDetailsBinding.favChip.setOnClickListener(v -> { + favoriteRepository.getFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + favoriteRepository.deleteFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + locationDetailsBinding.favChip.setText(R.string.add_to_favorites); + locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + showSnackbar(getString(R.string.removed_from_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + + @Override + public void onDataNotAvailable() { + favoriteRepository.insertOrUpdateFavorite(new Favorite( + -1, + locationId, + FavoriteType.LOCATION, + locationModel.getName(), + locationModel.getSdProfilePic(), + new Date() + ), new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + locationDetailsBinding.favChip.setText(R.string.favorite_short); + locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + showSnackbar(getString(R.string.added_to_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + }); }); locationDetailsBinding.mainLocationImage.setOnClickListener(v -> { if (hasStories) { @@ -487,6 +515,14 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR }); } + private void showSnackbar(final String message) { + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + } + private void fetchStories() { if (isLoggedIn) { storiesFetching = true; diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index 651bd8b3..f8733382 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -43,6 +43,8 @@ import awais.instagrabber.webservices.FriendshipService; import awais.instagrabber.webservices.NewsService; import awais.instagrabber.webservices.ServiceCallback; +import static awais.instagrabber.utils.Utils.settingsHelper; + public final class NotificationsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "NotificationsViewer"; @@ -189,10 +191,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe } private void init() { + final Context context = getContext(); + CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); binding.swipeRefreshLayout.setOnRefreshListener(this); notificationViewModel = new ViewModelProvider(this).get(NotificationViewModel.class); final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener); - binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rvComments.setLayoutManager(new LinearLayoutManager(context)); binding.rvComments.setAdapter(adapter); notificationViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); onRefresh(); diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 5743bdf7..2dc2238f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -63,6 +63,13 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; import awais.instagrabber.databinding.FragmentProfileBinding; import awais.instagrabber.databinding.LayoutProfileDetailsBinding; +import awais.instagrabber.db.datasources.AccountDataSource; +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.repositories.AccountRepository; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.fragments.PostViewV2Fragment; @@ -75,9 +82,9 @@ import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -283,6 +290,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } }; private LayoutProfileDetailsBinding profileDetailsBinding; + private AccountRepository accountRepository; + private FavoriteRepository favoriteRepository; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -290,6 +299,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe fragmentActivity = (MainActivity) requireActivity(); friendshipService = FriendshipService.getInstance(); storiesService = StoriesService.getInstance(); + final AppExecutors appExecutors = new AppExecutors(); + accountRepository = AccountRepository.getInstance(appExecutors, AccountDataSource.getInstance(getContext())); + favoriteRepository = FavoriteRepository.getInstance(appExecutors, FavoriteDataSource.getInstance(getContext())); setHasOptionsMenu(true); } @@ -498,19 +510,26 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe setUsernameDelayed(); fetchProfileDetails(); }; - boolean found = false; - final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid); - if (cookieModel != null) { - final String username = cookieModel.getUsername(); - if (!TextUtils.isEmpty(username)) { - found = true; - fetchListener.onResult("@" + username); + accountRepository.getAccount(uid, new RepositoryCallback() { + @Override + public void onSuccess(final Account account) { + boolean found = false; + if (account != null) { + final String username = account.getUsername(); + if (!TextUtils.isEmpty(username)) { + found = true; + fetchListener.onResult("@" + username); + } + } + if (!found) { + // if not in database, fetch info from instagram + new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } } - } - if (!found) { - // if not in database, fetch info from instagram - new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } + + @Override + public void onDataNotAvailable() {} + }); return; } fetchProfileDetails(); @@ -556,9 +575,19 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe setupButtons(profileId, myId); if (!profileId.equals(myId)) { profileDetailsBinding.favCb.setVisibility(View.VISIBLE); - final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null; - profileDetailsBinding.favCb.setChecked(isFav); - profileDetailsBinding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24); + favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + profileDetailsBinding.favCb.setChecked(true); + profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); + } + + @Override + public void onDataNotAvailable() { + profileDetailsBinding.favCb.setChecked(false); + profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); + } + }); } else { profileDetailsBinding.favCb.setVisibility(View.GONE); } @@ -842,41 +871,67 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); profileDetailsBinding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> { // do not do anything if state matches the db, as listener is set before profile details are set + final Context context = getContext(); + if (context == null) return; final String finalUsername = username.startsWith("@") ? username.substring(1) : username; - final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER); - if ((isChecked && favorite != null) || (!isChecked && favorite == null)) { - return; - } - buttonView.setVisibility(View.GONE); - profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); - final String message; - if (isChecked) { - final DataBox.FavoriteModel model = new DataBox.FavoriteModel( - -1, - finalUsername, - FavoriteType.USER, - profileModel.getName(), - profileModel.getSdProfilePic(), - new Date() - ); - Utils.dataBox.addOrUpdateFavorite(model); - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); - message = getString(R.string.added_to_favs); - } else { - Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER); - message = getString(R.string.removed_from_favs); - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); - } - final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); - snackbar.setAction(R.string.ok, v -> snackbar.dismiss()) - .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) - .setAnchorView(fragmentActivity.getBottomNavView()) - .show(); - profileDetailsBinding.favProgress.setVisibility(View.GONE); - profileDetailsBinding.favCb.setVisibility(View.VISIBLE); + favoriteRepository.getFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + if (isChecked) return; // already a fav + buttonView.setVisibility(View.GONE); + profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); + favoriteRepository.deleteFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); + profileDetailsBinding.favProgress.setVisibility(View.GONE); + profileDetailsBinding.favCb.setVisibility(View.VISIBLE); + showSnackbar(getString(R.string.removed_from_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + + @Override + public void onDataNotAvailable() { + if (!isChecked) return; // not in fav already + buttonView.setVisibility(View.GONE); + profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); + final Favorite model = new Favorite( + -1, + finalUsername, + FavoriteType.USER, + profileModel.getName(), + profileModel.getSdProfilePic(), + new Date() + ); + favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); + profileDetailsBinding.favProgress.setVisibility(View.GONE); + profileDetailsBinding.favCb.setVisibility(View.VISIBLE); + showSnackbar(getString(R.string.added_to_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + }); }); } + private void showSnackbar(final String message) { + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + } + private void showProfilePicDialog() { if (profileModel != null) { final FragmentManager fragmentManager = getParentFragmentManager(); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 74cb4edd..3c8398ea 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -8,6 +8,7 @@ import android.util.Log; import android.view.View; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -26,14 +27,17 @@ import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.activities.Login; import awais.instagrabber.databinding.PrefAccountSwitcherBinding; +import awais.instagrabber.db.datasources.AccountDataSource; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.db.repositories.AccountRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.AccountSwitcherDialogFragment; import awais.instagrabber.repositories.responses.UserInfo; +import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.FlavorTown; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.ProfileService; import awais.instagrabber.webservices.ServiceCallback; @@ -42,20 +46,23 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class MorePreferencesFragment extends BasePreferencesFragment { private static final String TAG = "MorePreferencesFragment"; + private final AccountRepository accountRepository; + + public MorePreferencesFragment() { + accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + } @Override void setupPreferenceScreen(final PreferenceScreen screen) { final String cookie = settingsHelper.getString(Constants.COOKIE); final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; // screen.addPreference(new MoreHeaderPreference(getContext())); - final Context context = getContext(); if (context == null) return; final PreferenceCategory accountCategory = new PreferenceCategory(context); accountCategory.setTitle(R.string.account); accountCategory.setIconSpaceReserved(false); screen.addPreference(accountCategory); - final List allCookies = Utils.dataBox.getAllCookies(); if (isLoggedIn) { accountCategory.setSummary(R.string.account_hint); accountCategory.addPreference(getAccountSwitcherPreference(cookie)); @@ -67,34 +74,59 @@ public class MorePreferencesFragment extends BasePreferencesFragment { settingsHelper.putString(Constants.COOKIE, ""); return true; })); - } else { - if (allCookies != null && allCookies.size() > 0) { - accountCategory.addPreference(getAccountSwitcherPreference(null)); - } - // Need to show something to trigger login activity - accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> { - startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE); - return true; - })); } + accountRepository.getAllAccounts(new RepositoryCallback>() { + @Override + public void onSuccess(@NonNull final List accounts) { + if (!isLoggedIn) { + if (accounts.size() > 0) { + accountCategory.addPreference(getAccountSwitcherPreference(null)); + } + // Need to show something to trigger login activity + accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> { + startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE); + return true; + })); + } + if (accounts.size() > 0) { + accountCategory + .addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> { + if (getContext() == null) return false; + new AlertDialog.Builder(getContext()) + .setTitle(R.string.logout) + .setMessage(R.string.remove_all_acc_warning) + .setPositiveButton(R.string.yes, (dialog, which) -> { + CookieUtils.removeAllAccounts(context, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + shouldRecreate(); + Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show(); + settingsHelper.putString(Constants.COOKIE, ""); + } - if (allCookies != null && allCookies.size() > 0) { - accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> { - if (getContext() == null) return false; - new AlertDialog.Builder(getContext()) - .setTitle(R.string.logout) - .setMessage(R.string.remove_all_acc_warning) - .setPositiveButton(R.string.yes, (dialog, which) -> { - CookieUtils.setupCookies("REMOVE"); - shouldRecreate(); - Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show(); - settingsHelper.putString(Constants.COOKIE, ""); - }) - .setNegativeButton(R.string.cancel, null) - .show(); - return true; - })); - } + @Override + public void onDataNotAvailable() {} + }); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + return true; + })); + } + } + + @Override + public void onDataNotAvailable() { + Log.d(TAG, "onDataNotAvailable"); + if (!isLoggedIn) { + // Need to show something to trigger login activity + accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> { + startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE); + return true; + })); + } + } + }); // final PreferenceCategory generalCategory = new PreferenceCategory(context); // generalCategory.setTitle(R.string.pref_category_general); @@ -163,11 +195,26 @@ public class MorePreferencesFragment extends BasePreferencesFragment { public void onSuccess(final UserInfo result) { // Log.d(TAG, "adding userInfo: " + result); if (result != null) { - Utils.dataBox.addOrUpdateUser(uid, result.getUsername(), cookie, result.getFullName(), result.getProfilePicUrl()); + accountRepository.insertOrUpdateAccount( + uid, + result.getUsername(), + cookie, + result.getFullName(), + result.getProfilePicUrl(), + new RepositoryCallback() { + @Override + public void onSuccess(final Account result) { + final FragmentActivity activity = getActivity(); + if (activity == null) return; + activity.recreate(); + } + + @Override + public void onDataNotAvailable() { + Log.e(TAG, "onDataNotAvailable: insert failed"); + } + }); } - final FragmentActivity activity = getActivity(); - if (activity == null) return; - activity.recreate(); } @Override @@ -181,7 +228,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment { private AccountSwitcherPreference getAccountSwitcherPreference(final String cookie) { final Context context = getContext(); if (context == null) return null; - return new AccountSwitcherPreference(context, cookie, v -> showAccountSwitcherDialog()); + return new AccountSwitcherPreference(context, cookie, accountRepository, v -> showAccountSwitcherDialog()); } private void showAccountSwitcherDialog() { @@ -244,13 +291,16 @@ public class MorePreferencesFragment extends BasePreferencesFragment { public static class AccountSwitcherPreference extends Preference { private final String cookie; + private final AccountRepository accountRepository; private final View.OnClickListener onClickListener; public AccountSwitcherPreference(final Context context, final String cookie, + final AccountRepository accountRepository, final View.OnClickListener onClickListener) { super(context); this.cookie = cookie; + this.accountRepository = accountRepository; this.onClickListener = onClickListener; setLayoutResource(R.layout.pref_account_switcher); } @@ -263,11 +313,20 @@ public class MorePreferencesFragment extends BasePreferencesFragment { final PrefAccountSwitcherBinding binding = PrefAccountSwitcherBinding.bind(root); final String uid = CookieUtils.getUserIdFromCookie(cookie); if (uid == null) return; - final DataBox.CookieModel user = Utils.dataBox.getCookie(uid); - if (user == null) return; - binding.fullName.setText(user.getFullName()); - binding.username.setText("@" + user.getUsername()); - binding.profilePic.setImageURI(user.getProfilePic()); + accountRepository.getAccount(uid, new RepositoryCallback() { + @Override + public void onSuccess(final Account account) { + binding.getRoot().post(() -> { + binding.fullName.setText(account.getFullName()); + binding.username.setText("@" + account.getUsername()); + binding.profilePic.setImageURI(account.getProfilePic()); + binding.getRoot().requestLayout(); + }); + } + + @Override + public void onDataNotAvailable() {} + }); } } } diff --git a/app/src/main/java/awais/instagrabber/utils/AppExecutors.java b/app/src/main/java/awais/instagrabber/utils/AppExecutors.java new file mode 100644 index 00000000..ca5b498b --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/AppExecutors.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package awais.instagrabber.utils; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Global executor pools for the whole application. + *

+ * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind + * webservice requests). + */ +public class AppExecutors { + + // private static final int THREAD_COUNT = 3; + + private final Executor diskIO; + // private final Executor networkIO; + private final Executor mainThread; + private final ListeningExecutorService tasksThread; + + private AppExecutors(Executor diskIO, + // Executor networkIO, + Executor mainThread, + ListeningExecutorService tasksThread) { + this.diskIO = diskIO; + // this.networkIO = networkIO; + this.mainThread = mainThread; + this.tasksThread = tasksThread; + } + + public AppExecutors() { + this(Executors.newSingleThreadExecutor(), + // Executors.newFixedThreadPool(THREAD_COUNT), + new MainThreadExecutor(), + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10))); + } + + public Executor diskIO() { + return diskIO; + } + + // public Executor networkIO() { + // return networkIO; + // } + + + public ListeningExecutorService tasksThread() { + return tasksThread; + } + + public Executor mainThread() { + return mainThread; + } + + private static class MainThreadExecutor implements Executor { + private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(@NonNull Runnable command) { + mainThreadHandler.post(command); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/CookieUtils.java b/app/src/main/java/awais/instagrabber/utils/CookieUtils.java index 918dcf38..5a1a5e12 100644 --- a/app/src/main/java/awais/instagrabber/utils/CookieUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/CookieUtils.java @@ -1,5 +1,6 @@ package awais.instagrabber.utils; +import android.content.Context; import android.util.Log; import android.webkit.CookieManager; @@ -17,9 +18,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.datasources.AccountDataSource; +import awais.instagrabber.db.repositories.AccountRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awaisomereport.LogCollector; public final class CookieUtils { + private static final String TAG = CookieUtils.class.getSimpleName(); public static final CookieManager COOKIE_MANAGER = CookieManager.getInstance(); public static final java.net.CookieManager NET_COOKIE_MANAGER = new java.net.CookieManager(null, CookiePolicy.ACCEPT_ALL); @@ -28,11 +33,7 @@ public final class CookieUtils { if (cookieStore == null || TextUtils.isEmpty(cookieRaw)) { return; } - if (cookieRaw.equals("REMOVE")) { - cookieStore.removeAll(); - Utils.dataBox.deleteAllUserCookies(); - return; - } else if (cookieRaw.equals("LOGOUT")) { + if (cookieRaw.equals("LOGOUT")) { cookieStore.removeAll(); return; } @@ -53,7 +54,19 @@ public final class CookieUtils { } catch (final URISyntaxException e) { if (Utils.logCollector != null) Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "setupCookies"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + } + + public static void removeAllAccounts(final Context context, final RepositoryCallback callback) { + final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore(); + if (cookieStore == null) return; + cookieStore.removeAll(); + try { + AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)) + .deleteAllAccounts(callback); + } catch (Exception e) { + Log.e(TAG, "setupCookies", e); } } diff --git a/app/src/main/java/awais/instagrabber/utils/DataBox.java b/app/src/main/java/awais/instagrabber/utils/DataBox.java index 73e71b04..3df698f3 100755 --- a/app/src/main/java/awais/instagrabber/utils/DataBox.java +++ b/app/src/main/java/awais/instagrabber/utils/DataBox.java @@ -10,17 +10,13 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.util.ObjectsCompat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.models.enums.FavoriteType; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.Utils.logCollector; public final class DataBox extends SQLiteOpenHelper { private static final String TAG = "DataBox"; @@ -28,22 +24,22 @@ public final class DataBox extends SQLiteOpenHelper { private static DataBox sInstance; private final static int VERSION = 3; - private final static String TABLE_COOKIES = "cookies"; - private final static String TABLE_FAVORITES = "favorites"; + public final static String TABLE_COOKIES = "cookies"; + public final static String TABLE_FAVORITES = "favorites"; - private final static String KEY_ID = "id"; - private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; - private final static String KEY_COOKIE = "cookie"; - private final static String KEY_UID = "uid"; - private final static String KEY_FULL_NAME = "full_name"; - private final static String KEY_PROFILE_PIC = "profile_pic"; + public final static String KEY_ID = "id"; + public final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; + public final static String KEY_COOKIE = "cookie"; + public final static String KEY_UID = "uid"; + public final static String KEY_FULL_NAME = "full_name"; + public final static String KEY_PROFILE_PIC = "profile_pic"; - private final static String FAV_COL_ID = "id"; - private final static String FAV_COL_QUERY = "query_text"; - private final static String FAV_COL_TYPE = "type"; - private final static String FAV_COL_DISPLAY_NAME = "display_name"; - private final static String FAV_COL_PIC_URL = "pic_url"; - private final static String FAV_COL_DATE_ADDED = "date_added"; + public final static String FAV_COL_ID = "id"; + public final static String FAV_COL_QUERY = "query_text"; + public final static String FAV_COL_TYPE = "type"; + public final static String FAV_COL_DISPLAY_NAME = "display_name"; + public final static String FAV_COL_PIC_URL = "pic_url"; + public final static String FAV_COL_DATE_ADDED = "date_added"; public static synchronized DataBox getInstance(final Context context) { if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); @@ -84,7 +80,7 @@ public final class DataBox extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); case 2: - final List oldFavorites = backupOldFavorites(db); + final List oldFavorites = backupOldFavorites(db); // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) db.execSQL("DROP TABLE " + TABLE_FAVORITES); db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" @@ -95,19 +91,41 @@ public final class DataBox extends SQLiteOpenHelper { + FAV_COL_PIC_URL + " TEXT," + FAV_COL_DATE_ADDED + " INTEGER)"); // add the old favorites back - for (final FavoriteModel oldFavorite : oldFavorites) { - addOrUpdateFavorite(db, oldFavorite); + for (final Favorite oldFavorite : oldFavorites) { + insertOrUpdateFavorite(db, oldFavorite); } } Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); } + public synchronized void insertOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final Favorite model) { + final ContentValues values = new ContentValues(); + values.put(FAV_COL_QUERY, model.getQuery()); + values.put(FAV_COL_TYPE, model.getType().toString()); + values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); + values.put(FAV_COL_PIC_URL, model.getPicUrl()); + values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); + int rows; + if (model.getId() >= 1) { + rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); + } else { + rows = db.update(TABLE_FAVORITES, + values, + FAV_COL_QUERY + "=?" + + " AND " + FAV_COL_TYPE + "=?", + new String[]{model.getQuery(), model.getType().toString()}); + } + if (rows != 1) { + db.insert(TABLE_FAVORITES, null, values); + } + } + @NonNull - private List backupOldFavorites(@NonNull final SQLiteDatabase db) { + private List backupOldFavorites(@NonNull final SQLiteDatabase db) { // check if old favorites table had the column query_display final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display"); Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); - final List oldModels = new ArrayList<>(); + final List oldModels = new ArrayList<>(); final String sql = "SELECT " + "query_text," + "date_added" @@ -122,7 +140,7 @@ public final class DataBox extends SQLiteOpenHelper { if (favoriteTypeQueryPair == null) continue; final FavoriteType type = favoriteTypeQueryPair.first; final String query = favoriteTypeQueryPair.second; - oldModels.add(new FavoriteModel( + oldModels.add(new Favorite( -1, query, type, @@ -162,434 +180,4 @@ public final class DataBox extends SQLiteOpenHelper { } return exists; } - - public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) { - final String query = model.getQuery(); - if (!TextUtils.isEmpty(query)) { - try (final SQLiteDatabase db = getWritableDatabase()) { - db.beginTransaction(); - try { - addOrUpdateFavorite(db, model); - db.setTransactionSuccessful(); - } catch (final Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite"); - } - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error adding/updating favorite", e); - } - } finally { - db.endTransaction(); - } - } - } - } - - private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) { - final ContentValues values = new ContentValues(); - values.put(FAV_COL_QUERY, model.getQuery()); - values.put(FAV_COL_TYPE, model.getType().toString()); - values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); - values.put(FAV_COL_PIC_URL, model.getPicUrl()); - values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); - int rows; - if (model.getId() >= 1) { - rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); - } else { - rows = db.update(TABLE_FAVORITES, - values, - FAV_COL_QUERY + "=?" + - " AND " + FAV_COL_TYPE + "=?", - new String[]{model.getQuery(), model.getType().toString()}); - } - if (rows != 1) { - db.insertOrThrow(TABLE_FAVORITES, null, values); - } - } - - public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { - if (!TextUtils.isEmpty(query)) { - try (final SQLiteDatabase db = getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_FAVORITES, - FAV_COL_QUERY + "=?" + - " AND " + FAV_COL_TYPE + "=?", - new String[]{query, type.toString()}); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite"); - } - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error", e); - } - } finally { - db.endTransaction(); - } - } - } - } - - @NonNull - public final List getAllFavorites() { - final List favorites = new ArrayList<>(); - final SQLiteDatabase db = getWritableDatabase(); - try (final Cursor cursor = db.rawQuery("SELECT " - + FAV_COL_ID + "," - + FAV_COL_QUERY + "," - + FAV_COL_TYPE + "," - + FAV_COL_DISPLAY_NAME + "," - + FAV_COL_PIC_URL + "," - + FAV_COL_DATE_ADDED - + " FROM " + TABLE_FAVORITES, - null)) { - if (cursor != null && cursor.moveToFirst()) { - db.beginTransaction(); - FavoriteModel tempFav; - do { - FavoriteType type = null; - try { - type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); - } catch (IllegalArgumentException ignored) {} - tempFav = new FavoriteModel( - cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), - cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), - type, - cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), - cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), - new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) - ); - favorites.add(tempFav); - } while (cursor.moveToNext()); - db.endTransaction(); - } - } catch (final Exception e) { - Log.e(TAG, "", e); - } - return favorites; - } - - @Nullable - public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) { - try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery("SELECT " - + FAV_COL_ID + "," - + FAV_COL_QUERY + "," - + FAV_COL_TYPE + "," - + FAV_COL_DISPLAY_NAME + "," - + FAV_COL_PIC_URL + "," - + FAV_COL_DATE_ADDED - + " FROM " + TABLE_FAVORITES - + " WHERE " + FAV_COL_QUERY + "='" + query + "'" - + " AND " + FAV_COL_TYPE + "='" + type.toString() + "'", - null)) { - if (cursor != null && cursor.moveToFirst()) { - FavoriteType favoriteType = null; - try { - favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); - } catch (IllegalArgumentException ignored) {} - return new FavoriteModel( - cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), - cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), - favoriteType, - cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), - cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), - new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) - ); - } - } - return null; - } - - public final void addOrUpdateUser(@NonNull final DataBox.CookieModel cookieModel) { - addOrUpdateUser( - cookieModel.getUid(), - cookieModel.getUsername(), - cookieModel.getCookie(), - cookieModel.getFullName(), - cookieModel.getProfilePic() - ); - } - - public final void addOrUpdateUser(final String uid, - final String username, - final String cookie, - final String fullName, - final String profilePicUrl) { - if (TextUtils.isEmpty(uid)) return; - try (final SQLiteDatabase db = getWritableDatabase()) { - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - values.put(KEY_USERNAME, username); - values.put(KEY_COOKIE, cookie); - values.put(KEY_UID, uid); - values.put(KEY_FULL_NAME, fullName); - values.put(KEY_PROFILE_PIC, profilePicUrl); - - final int rows = db.update(TABLE_COOKIES, values, KEY_UID + "=?", new String[]{uid}); - - if (rows != 1) - db.insertOrThrow(TABLE_COOKIES, null, values); - - db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); - } finally { - db.endTransaction(); - } - } - } - - public final synchronized void delUserCookie(@NonNull final CookieModel cookieModel) { - final String cookieModelUid = cookieModel.getUid(); - if (!TextUtils.isEmpty(cookieModelUid)) { - try (final SQLiteDatabase db = getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_COOKIES, KEY_UID + "=? AND " + KEY_USERNAME + "=? AND " + KEY_COOKIE + "=?", - new String[]{cookieModelUid, cookieModel.getUsername(), cookieModel.getCookie()}); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } finally { - db.endTransaction(); - } - } - } - } - - public final synchronized void deleteAllUserCookies() { - try (final SQLiteDatabase db = getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_COOKIES, null, null); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } finally { - db.endTransaction(); - } - } - } - - @Nullable - public final CookieModel getCookie(final String uid) { - CookieModel cookie = null; - try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery( - "SELECT " - + KEY_UID + "," - + KEY_USERNAME + "," - + KEY_COOKIE + "," - + KEY_FULL_NAME + "," - + KEY_PROFILE_PIC - + " FROM " + TABLE_COOKIES - + " WHERE " + KEY_UID + " = ?", - new String[]{uid}) - ) { - if (cursor != null && cursor.moveToFirst()) - cookie = new CookieModel( - cursor.getString(cursor.getColumnIndex(KEY_UID)), - cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), - cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), - cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), - cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) - ); - } - return cookie; - } - - @NonNull - public final List getAllCookies() { - final List cookies = new ArrayList<>(); - try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery( - "SELECT " - + KEY_UID + "," - + KEY_USERNAME + "," - + KEY_COOKIE + "," - + KEY_FULL_NAME + "," - + KEY_PROFILE_PIC - + " FROM " + TABLE_COOKIES, null) - ) { - if (cursor != null && cursor.moveToFirst()) { - do { - cookies.add(new CookieModel( - cursor.getString(cursor.getColumnIndex(KEY_UID)), - cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), - cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), - cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), - cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) - )); - } while (cursor.moveToNext()); - } - } - return cookies; - } - - public static class CookieModel { - private final String uid; - private final String username; - private final String cookie; - private final String fullName; - private final String profilePic; - private boolean selected; - - public CookieModel(final String uid, - final String username, - final String cookie, - final String fullName, - final String profilePic) { - this.uid = uid; - this.username = username; - this.cookie = cookie; - this.fullName = fullName; - this.profilePic = profilePic; - } - - public String getUid() { - return uid; - } - - public String getUsername() { - return username; - } - - public String getCookie() { - return cookie; - } - - public String getFullName() { - return fullName; - } - - public String getProfilePic() { - return profilePic; - } - - public boolean isSelected() { - return selected; - } - - public void setSelected(final boolean selected) { - this.selected = selected; - } - - public boolean isValid() { - return !TextUtils.isEmpty(uid) - && !TextUtils.isEmpty(username) - && !TextUtils.isEmpty(cookie); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final CookieModel that = (CookieModel) o; - return ObjectsCompat.equals(uid, that.uid) && - ObjectsCompat.equals(username, that.username) && - ObjectsCompat.equals(cookie, that.cookie); - } - - @Override - public int hashCode() { - return ObjectsCompat.hash(uid, username, cookie); - } - - @NonNull - @Override - public String toString() { - return "CookieModel{" + - "uid='" + uid + '\'' + - ", username='" + username + '\'' + - ", cookie='" + cookie + '\'' + - ", fullName='" + fullName + '\'' + - ", profilePic='" + profilePic + '\'' + - ", selected=" + selected + - '}'; - } - } - - public static class FavoriteModel { - private final int id; - private final String query; - private final FavoriteType type; - private final String displayName; - private final String picUrl; - private final Date dateAdded; - - public FavoriteModel(final int id, - final String query, - final FavoriteType type, - final String displayName, - final String picUrl, - final Date dateAdded) { - this.id = id; - this.query = query; - this.type = type; - this.displayName = displayName; - this.picUrl = picUrl; - this.dateAdded = dateAdded; - } - - public int getId() { - return id; - } - - public String getQuery() { - return query; - } - - public FavoriteType getType() { - return type; - } - - public String getDisplayName() { - return displayName; - } - - public String getPicUrl() { - return picUrl; - } - - public Date getDateAdded() { - return dateAdded; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final FavoriteModel that = (FavoriteModel) o; - return id == that.id && - ObjectsCompat.equals(query, that.query) && - type == that.type && - ObjectsCompat.equals(displayName, that.displayName) && - ObjectsCompat.equals(picUrl, that.picUrl) && - ObjectsCompat.equals(dateAdded, that.dateAdded); - } - - @Override - public int hashCode() { - return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded); - } - - @NonNull - @Override - public String toString() { - return "FavoriteModel{" + - "id=" + id + - ", query='" + query + '\'' + - ", type=" + type + - ", displayName='" + displayName + '\'' + - ", picUrl='" + picUrl + '\'' + - ", dateAdded=" + dateAdded + - '}'; - } - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 3770bd65..5f195562 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -2,6 +2,7 @@ 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; @@ -18,12 +19,21 @@ import org.json.JSONObject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.util.ArrayList; 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; +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.repositories.AccountRepository; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException; @@ -47,37 +57,38 @@ public final class ExportImportUtils { @NonNull final File filePath, final FetchListener fetchListener, @NonNull final Context context) { - final String exportString = getExportString(flags, context); - 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); + 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); } - } 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); + 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, @@ -99,7 +110,8 @@ public final class ExportImportUtils { final byte[] passwordBytes = password.getBytes(); final byte[] bytes = new byte[32]; System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); - importJson(new String(PasswordUtils.dec(builder.toString(), bytes)), + importJson(context, + new String(PasswordUtils.dec(builder.toString(), bytes)), flags, fetchListener); } catch (final IncorrectPasswordException e) { @@ -111,7 +123,8 @@ public final class ExportImportUtils { if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e); } } else if (configType == 'Z') { - importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), + importJson(context, + new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), flags, fetchListener); @@ -129,7 +142,8 @@ public final class ExportImportUtils { } } - private static void importJson(@NonNull final String json, + private static void importJson(final Context context, + @NonNull final String json, @ExportImportFlags final int flags, final FetchListener fetchListener) { try { @@ -138,10 +152,10 @@ public final class ExportImportUtils { importSettings(jsonObject); } if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) { - importAccounts(jsonObject); + importAccounts(context, jsonObject); } if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) { - importFavorites(jsonObject); + importFavorites(context, jsonObject); } if (fetchListener != null) fetchListener.onResult(true); } catch (final Exception e) { @@ -151,7 +165,7 @@ public final class ExportImportUtils { } } - private static void importFavorites(final JSONObject jsonObject) throws JSONException { + private static void importFavorites(final Context context, final JSONObject jsonObject) throws JSONException { final JSONArray favs = jsonObject.getJSONArray("favs"); for (int i = 0; i < favs.length(); i++) { final JSONObject favsObject = favs.getJSONObject(i); @@ -175,7 +189,7 @@ public final class ExportImportUtils { if (query == null || favoriteType == null) { continue; } - final DataBox.FavoriteModel favoriteModel = new DataBox.FavoriteModel( + final Favorite favorite = new Favorite( -1, query, favoriteType, @@ -184,41 +198,55 @@ public final class ExportImportUtils { : favsObject.optString("pic_url"), new Date(favsObject.getLong("d"))); // Log.d(TAG, "importJson: favoriteModel: " + favoriteModel); - Utils.dataBox.addOrUpdateFavorite(favoriteModel); + FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(context)) + .insertOrUpdateFavorite(favorite, null); } } - private static void importAccounts(final JSONObject jsonObject) throws JSONException { - final JSONArray cookies = jsonObject.getJSONArray("cookies"); - for (int i = 0; i < cookies.length(); i++) { - final JSONObject cookieObject = cookies.getJSONObject(i); - final DataBox.CookieModel cookieModel = new DataBox.CookieModel( - cookieObject.optString("i"), - cookieObject.optString("u"), - cookieObject.optString("c"), - cookieObject.optString("full_name"), - cookieObject.optString("profile_pic") - ); - if (!cookieModel.isValid()) continue; - // Log.d(TAG, "importJson: cookieModel: " + cookieModel); - Utils.dataBox.addOrUpdateUser(cookieModel); - } - } - - private static void importSettings(final JSONObject jsonObject) throws JSONException { - final JSONObject objSettings = jsonObject.getJSONObject("settings"); - final Iterator keys = objSettings.keys(); - while (keys.hasNext()) { - final String key = keys.next(); - final Object val = objSettings.opt(key); - // Log.d(TAG, "importJson: key: " + key + ", val: " + val); - if (val instanceof String) { - settingsHelper.putString(key, (String) val); - } else if (val instanceof Integer) { - settingsHelper.putInteger(key, (int) val); - } else if (val instanceof Boolean) { - settingsHelper.putBoolean(key, (boolean) val); + private static void importAccounts(final Context context, + final JSONObject jsonObject) { + final List accounts = new ArrayList<>(); + try { + final JSONArray cookies = jsonObject.getJSONArray("cookies"); + for (int i = 0; i < cookies.length(); i++) { + final JSONObject cookieObject = cookies.getJSONObject(i); + final Account account = new Account( + -1, + cookieObject.optString("i"), + cookieObject.optString("u"), + cookieObject.optString("c"), + cookieObject.optString("full_name"), + cookieObject.optString("profile_pic") + ); + if (!account.isValid()) continue; + accounts.add(account); } + } catch (Exception e) { + Log.e(TAG, "importAccounts: Error parsing json", e); + return; + } + AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)) + .insertOrUpdateAccounts(accounts, null); + } + + private static void importSettings(final JSONObject jsonObject) { + try { + final JSONObject objSettings = jsonObject.getJSONObject("settings"); + final Iterator keys = objSettings.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + final Object val = objSettings.opt(key); + // Log.d(TAG, "importJson: key: " + key + ", val: " + val); + if (val instanceof String) { + settingsHelper.putString(key, (String) val); + } else if (val instanceof Integer) { + settingsHelper.putInteger(key, (int) val); + } else if (val instanceof Boolean) { + settingsHelper.putBoolean(key, (boolean) val); + } + } + } catch (Exception e) { + Log.e(TAG, "importSettings error", e); } } @@ -234,28 +262,62 @@ public final class ExportImportUtils { return false; } - @Nullable - private static String getExportString(@ExportImportFlags final int flags, - @NonNull final Context context) { - String result = null; - try { - final JSONObject jsonObject = new JSONObject(); - if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { - jsonObject.put("settings", getSettings(context)); + //todo Need to improve logic + private static void getExportString(@ExportImportFlags final int flags, + @NonNull final Context context, + final OnExportStringCreatedCallback callback) { + final AppExecutors appExecutors = new AppExecutors(); + final Handler innerHandler = new Handler(); + appExecutors.tasksThread().execute(() -> { + final CountDownLatch responseWaiter = new CountDownLatch(3); + try { + final JSONObject jsonObject = new JSONObject(); + innerHandler.post(() -> { + if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { + try { + jsonObject.put("settings", getSettings(context)); + } catch (JSONException e) { + Log.e(TAG, "getExportString: ", 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); + } + responseWaiter.countDown(); + }); + return; + } + 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); + } + responseWaiter.countDown(); + }); + return; + } + responseWaiter.countDown(); + }); + responseWaiter.await(); + callback.onCreated(jsonObject.toString()); + } catch (final Exception e) { + if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } - if ((flags & FLAG_COOKIES) == FLAG_COOKIES) { - jsonObject.put("cookies", getCookies()); - } - if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) { - jsonObject.put("favs", getFavorites()); - } - - result = jsonObject.toString(); - } catch (final Exception e) { - if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } - return result; + callback.onCreated(null); + }); } @NonNull @@ -277,22 +339,35 @@ public final class ExportImportUtils { return new JSONObject(); } - @NonNull - private static JSONArray getFavorites() { - if (Utils.dataBox == null) return new JSONArray(); + private static void getFavorites(final Context context, final OnFavoritesJsonLoadedCallback callback) { + final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource); try { - final List allFavorites = Utils.dataBox.getAllFavorites(); - final JSONArray jsonArray = new JSONArray(); - for (final DataBox.FavoriteModel favorite : allFavorites) { - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("q", favorite.getQuery()); - jsonObject.put("type", favorite.getType().toString()); - jsonObject.put("s", favorite.getDisplayName()); - jsonObject.put("pic_url", favorite.getPicUrl()); - jsonObject.put("d", favorite.getDateAdded().getTime()); - jsonArray.put(jsonObject); - } - return jsonArray; + favoriteRepository.getAllFavorites(new RepositoryCallback>() { + @Override + public void onSuccess(final List favorites) { + final JSONArray jsonArray = new JSONArray(); + try { + for (final Favorite favorite : favorites) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("q", favorite.getQuery()); + jsonObject.put("type", favorite.getType().toString()); + jsonObject.put("s", favorite.getDisplayName()); + jsonObject.put("pic_url", favorite.getPicUrl()); + jsonObject.put("d", favorite.getDateAdded().getTime()); + 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"); @@ -301,30 +376,48 @@ public final class ExportImportUtils { Log.e(TAG, "Error exporting favorites", e); } } - return new JSONArray(); } - @NonNull - private static JSONArray getCookies() { - if (Utils.dataBox == null) return new JSONArray(); - try { - final List allCookies = Utils.dataBox.getAllCookies(); - final JSONArray jsonArray = new JSONArray(); - for (final DataBox.CookieModel cookie : allCookies) { - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("i", cookie.getUid()); - jsonObject.put("u", cookie.getUsername()); - jsonObject.put("c", cookie.getCookie()); - jsonObject.put("full_name", cookie.getFullName()); - jsonObject.put("profile_pic", cookie.getProfilePic()); - jsonArray.put(jsonObject); + private static void getCookies(final Context context, final OnAccountJsonLoadedCallback callback) { + final AccountRepository accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)); + accountRepository.getAllAccounts(new RepositoryCallback>() { + @Override + public void onSuccess(final List accounts) { + try { + final JSONArray jsonArray = new JSONArray(); + for (final Account cookie : accounts) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("i", cookie.getUid()); + jsonObject.put("u", cookie.getUsername()); + jsonObject.put("c", cookie.getCookie()); + jsonObject.put("full_name", cookie.getFullName()); + jsonObject.put("profile_pic", cookie.getProfilePic()); + jsonArray.put(jsonObject); + } + callback.onAccountsJsonLoaded(jsonArray); + return; + } catch (Exception e) { + Log.e(TAG, "Error exporting accounts", e); + } + callback.onAccountsJsonLoaded(new JSONArray()); } - return jsonArray; - } catch (final Exception e) { - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error exporting accounts", e); + + @Override + public void onDataNotAvailable() { + callback.onAccountsJsonLoaded(new JSONArray()); } - } - return new JSONArray(); + }); + } + + public interface OnExportStringCreatedCallback { + void onCreated(String exportString); + } + + public interface OnAccountJsonLoadedCallback { + void onAccountsJsonLoaded(JSONArray array); + } + + public interface OnFavoritesJsonLoadedCallback { + void onFavoritesJsonLoaded(JSONArray array); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 04cba7fc..786475f9 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -47,7 +47,6 @@ public final class Utils { public static LogCollector logCollector; public static SettingsHelper settingsHelper; - public static DataBox dataBox; public static boolean sessionVolumeFull = false; public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); public static ClipboardManager clipboardManager; diff --git a/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java index e30d4116..bb865b88 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java @@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel; import java.util.List; -import awais.instagrabber.utils.DataBox; +import awais.instagrabber.db.entities.Favorite; public class FavoritesViewModel extends ViewModel { - private MutableLiveData> list; + private MutableLiveData> list; - public MutableLiveData> getList() { + public MutableLiveData> getList() { if (list == null) { list = new MutableLiveData<>(); } diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index ff4a275d..ec72e8a1 100755 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -22,7 +22,7 @@ android:id="@+id/cookies" style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog" android:layout_width="0dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/get_cookies" /> @@ -30,14 +30,15 @@ android:id="@+id/refresh" style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog" android:layout_width="0dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/refresh" /> From 6b8df5fee2e2ff6daf11d606d1bf7ca202350cd7 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 8 Dec 2020 02:51:49 +0900 Subject: [PATCH 3/5] Migrate to Room --- .../awais/instagrabber/db/AppDatabase.java | 229 ++++++++++++++++++ .../awais/instagrabber/db/Converters.java | 33 +++ .../awais/instagrabber/db/dao/AccountDao.java | 34 +++ .../instagrabber/db/dao/FavoriteDao.java | 35 +++ .../db/datasources/AccountDataSource.java | 162 ++----------- .../db/datasources/FavoriteDataSource.java | 164 ++----------- .../instagrabber/db/entities/Account.java | 26 +- .../instagrabber/db/entities/Favorite.java | 23 +- .../db/repositories/AccountRepository.java | 4 +- .../db/repositories/FavoriteRepository.java | 14 +- .../AccountSwitcherDialogFragment.java | 5 +- .../fragments/FavoritesFragment.java | 11 +- .../fragments/HashTagFragment.java | 8 +- .../fragments/LocationFragment.java | 7 +- .../fragments/main/ProfileFragment.java | 10 +- .../settings/MorePreferencesFragment.java | 3 +- .../instagrabber/utils/AppExecutors.java | 35 ++- .../awais/instagrabber/utils/CookieUtils.java | 2 +- .../awais/instagrabber/utils/DataBox.java | 140 ----------- .../instagrabber/utils/ExportImportUtils.java | 11 +- 20 files changed, 465 insertions(+), 491 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/db/AppDatabase.java create mode 100644 app/src/main/java/awais/instagrabber/db/Converters.java create mode 100644 app/src/main/java/awais/instagrabber/db/dao/AccountDao.java create mode 100644 app/src/main/java/awais/instagrabber/db/dao/FavoriteDao.java diff --git a/app/src/main/java/awais/instagrabber/db/AppDatabase.java b/app/src/main/java/awais/instagrabber/db/AppDatabase.java new file mode 100644 index 00000000..6190575a --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/AppDatabase.java @@ -0,0 +1,229 @@ +package awais.instagrabber.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; +import androidx.room.TypeConverters; +import androidx.room.migration.Migration; +import androidx.sqlite.db.SupportSQLiteDatabase; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import awais.instagrabber.db.dao.AccountDao; +import awais.instagrabber.db.dao.FavoriteDao; +import awais.instagrabber.db.entities.Account; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.Utils; + +@Database(entities = {Account.class, Favorite.class}, + version = 4) +@TypeConverters({Converters.class}) +public abstract class AppDatabase extends RoomDatabase { + private static final String TAG = AppDatabase.class.getSimpleName(); + + private static AppDatabase INSTANCE; + + public abstract AccountDao accountDao(); + + public abstract FavoriteDao favoriteDao(); + + public static AppDatabase getDatabase(final Context context) { + if (INSTANCE == null) { + synchronized (AppDatabase.class) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db") + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) + .build(); + } + } + } + return INSTANCE; + } + + static final Migration MIGRATION_1_2 = new Migration(1, 2) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE cookies ADD " + Account.COL_FULL_NAME + " TEXT"); + db.execSQL("ALTER TABLE cookies ADD " + Account.COL_PROFILE_PIC + " TEXT"); + } + }; + + static final Migration MIGRATION_2_3 = new Migration(2, 3) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + final List oldFavorites = backupOldFavorites(db); + // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) + db.execSQL("DROP TABLE " + Favorite.TABLE_NAME); + db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + " (" + + Favorite.COL_ID + " INTEGER PRIMARY KEY," + + Favorite.COL_QUERY + " TEXT," + + Favorite.COL_TYPE + " TEXT," + + Favorite.COL_DISPLAY_NAME + " TEXT," + + Favorite.COL_PIC_URL + " TEXT," + + Favorite.COL_DATE_ADDED + " INTEGER)"); + // add the old favorites back + for (final Favorite oldFavorite : oldFavorites) { + insertOrUpdateFavorite(db, oldFavorite); + } + } + }; + + static final Migration MIGRATION_3_4 = new Migration(3, 4) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + // Required when migrating to Room. + // The original table primary keys were not 'NOT NULL', so the migration to Room were failing without the below migration. + // Taking this opportunity to rename cookies table to accounts + + // Create new table with name 'accounts' + db.execSQL("CREATE TABLE " + Account.TABLE_NAME + " (" + + Account.COL_ID + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + Account.COL_UID + " TEXT," + + Account.COL_USERNAME + " TEXT," + + Account.COL_COOKIE + " TEXT," + + Account.COL_FULL_NAME + " TEXT," + + Account.COL_PROFILE_PIC + " TEXT)"); + // Insert all data from table 'cookies' to 'accounts' + db.execSQL("INSERT INTO " + Account.TABLE_NAME + " (" + + Account.COL_UID + "," + + Account.COL_USERNAME + "," + + Account.COL_COOKIE + "," + + Account.COL_FULL_NAME + "," + + Account.COL_PROFILE_PIC + ") " + + "SELECT " + + Account.COL_UID + "," + + Account.COL_USERNAME + "," + + Account.COL_COOKIE + "," + + Account.COL_FULL_NAME + "," + + Account.COL_PROFILE_PIC + + " FROM cookies"); + // Drop old cookies table + db.execSQL("DROP TABLE cookies"); + + // Create favorite backup table + db.execSQL("CREATE TABLE " + Favorite.TABLE_NAME + "_backup (" + + Favorite.COL_ID + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + Favorite.COL_QUERY + " TEXT," + + Favorite.COL_TYPE + " TEXT," + + Favorite.COL_DISPLAY_NAME + " TEXT," + + Favorite.COL_PIC_URL + " TEXT," + + Favorite.COL_DATE_ADDED + " INTEGER)"); + // Insert all data from table 'favorite' to 'favorite_backup' + db.execSQL("INSERT INTO " + Favorite.TABLE_NAME + "_backup (" + + Favorite.COL_QUERY + "," + + Favorite.COL_TYPE + "," + + Favorite.COL_DISPLAY_NAME + "," + + Favorite.COL_PIC_URL + "," + + Favorite.COL_DATE_ADDED + ") " + + "SELECT " + + Favorite.COL_QUERY + "," + + Favorite.COL_TYPE + "," + + Favorite.COL_DISPLAY_NAME + "," + + Favorite.COL_PIC_URL + "," + + Favorite.COL_DATE_ADDED + + " FROM " + Favorite.TABLE_NAME); + // Drop favorites + db.execSQL("DROP TABLE " + Favorite.TABLE_NAME); + // Rename favorite_backup to favorites + db.execSQL("ALTER TABLE " + Favorite.TABLE_NAME + "_backup RENAME TO " + Favorite.TABLE_NAME); + } + }; + + @NonNull + private static List backupOldFavorites(@NonNull final SupportSQLiteDatabase db) { + // check if old favorites table had the column query_display + final boolean queryDisplayExists = checkColumnExists(db, Favorite.TABLE_NAME, "query_display"); + Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); + final List oldModels = new ArrayList<>(); + final String sql = "SELECT " + + "query_text," + + "date_added" + + (queryDisplayExists ? ",query_display" : "") + + " FROM " + Favorite.TABLE_NAME; + try (final Cursor cursor = db.query(sql)) { + if (cursor != null && cursor.moveToFirst()) { + do { + try { + final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); + final Pair favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText); + if (favoriteTypeQueryPair == null) continue; + final FavoriteType type = favoriteTypeQueryPair.first; + final String query = favoriteTypeQueryPair.second; + oldModels.add(new Favorite( + -1, + query, + type, + queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) + : null, + null, + new Date(cursor.getLong(cursor.getColumnIndex("date_added"))) + )); + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels); + return oldModels; + } + + private static synchronized void insertOrUpdateFavorite(@NonNull final SupportSQLiteDatabase db, @NonNull final Favorite model) { + final ContentValues values = new ContentValues(); + values.put(Favorite.COL_QUERY, model.getQuery()); + values.put(Favorite.COL_TYPE, model.getType().toString()); + values.put(Favorite.COL_DISPLAY_NAME, model.getDisplayName()); + values.put(Favorite.COL_PIC_URL, model.getPicUrl()); + values.put(Favorite.COL_DATE_ADDED, model.getDateAdded().getTime()); + int rows; + if (model.getId() >= 1) { + rows = db.update(Favorite.TABLE_NAME, + SQLiteDatabase.CONFLICT_IGNORE, + values, + Favorite.COL_ID + "=?", + new String[]{String.valueOf(model.getId())}); + } else { + rows = db.update(Favorite.TABLE_NAME, + SQLiteDatabase.CONFLICT_IGNORE, + values, + Favorite.COL_QUERY + "=?" + " AND " + Favorite.COL_TYPE + "=?", + new String[]{model.getQuery(), model.getType().toString()}); + } + if (rows != 1) { + db.insert(Favorite.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values); + } + } + + private static boolean checkColumnExists(@NonNull final SupportSQLiteDatabase db, + @NonNull final String tableName, + @NonNull final String columnName) { + boolean exists = false; + try (Cursor cursor = db.query("PRAGMA table_info(" + tableName + ")")) { + if (cursor.moveToFirst()) { + do { + final String currentColumn = cursor.getString(cursor.getColumnIndex("name")); + if (currentColumn.equals(columnName)) { + exists = true; + } + } while (cursor.moveToNext()); + + } + } catch (Exception ex) { + Log.e(TAG, "checkColumnExists", ex); + } + return exists; + } +} diff --git a/app/src/main/java/awais/instagrabber/db/Converters.java b/app/src/main/java/awais/instagrabber/db/Converters.java new file mode 100644 index 00000000..50252755 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/Converters.java @@ -0,0 +1,33 @@ +package awais.instagrabber.db; + +import androidx.room.TypeConverter; + +import java.util.Date; + +import awais.instagrabber.models.enums.FavoriteType; + +public class Converters { + @TypeConverter + public static Date fromTimestamp(Long value) { + return value == null ? null : new Date(value); + } + + @TypeConverter + public static Long dateToTimestamp(Date date) { + return date == null ? null : date.getTime(); + } + + @TypeConverter + public static FavoriteType fromFavoriteTypeString(String value) { + try { + return FavoriteType.valueOf(value); + } catch (Exception e) { + return null; + } + } + + @TypeConverter + public static String favoriteTypeToString(FavoriteType favoriteType) { + return favoriteType == null ? null : favoriteType.toString(); + } +} diff --git a/app/src/main/java/awais/instagrabber/db/dao/AccountDao.java b/app/src/main/java/awais/instagrabber/db/dao/AccountDao.java new file mode 100644 index 00000000..8524f192 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/dao/AccountDao.java @@ -0,0 +1,34 @@ +package awais.instagrabber.db.dao; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +import awais.instagrabber.db.entities.Account; + +@Dao +public interface AccountDao { + + @Query("SELECT * FROM accounts") + List getAllAccounts(); + + @Query("SELECT * FROM accounts WHERE uid = :uid") + Account findAccountByUid(String uid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + List insertAccounts(Account... accounts); + + @Update + void updateAccounts(Account... accounts); + + @Delete + void deleteAccounts(Account... accounts); + + @Query("DELETE from accounts") + void deleteAllAccounts(); +} diff --git a/app/src/main/java/awais/instagrabber/db/dao/FavoriteDao.java b/app/src/main/java/awais/instagrabber/db/dao/FavoriteDao.java new file mode 100644 index 00000000..50dc3129 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/dao/FavoriteDao.java @@ -0,0 +1,35 @@ +package awais.instagrabber.db.dao; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.models.enums.FavoriteType; + +@Dao +public interface FavoriteDao { + + @Query("SELECT * FROM favorites") + List getAllFavorites(); + + @Query("SELECT * FROM favorites WHERE query_text = :query and type = :type") + Favorite findFavoriteByQueryAndType(String query, FavoriteType type); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + List insertFavorites(Favorite... favorites); + + @Update + void updateFavorites(Favorite... favorites); + + @Delete + void deleteFavorites(Favorite... favorites); + + @Query("DELETE from favorites") + void deleteAllFavorites(); +} diff --git a/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java index 2aaa2f8a..a2ffe515 100644 --- a/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java +++ b/app/src/main/java/awais/instagrabber/db/datasources/AccountDataSource.java @@ -1,182 +1,68 @@ package awais.instagrabber.db.datasources; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.ArrayList; import java.util.List; -import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.AppDatabase; +import awais.instagrabber.db.dao.AccountDao; import awais.instagrabber.db.entities.Account; -import awais.instagrabber.utils.DataBox; -import awais.instagrabber.utils.TextUtils; - -import static awais.instagrabber.utils.DataBox.KEY_COOKIE; -import static awais.instagrabber.utils.DataBox.KEY_FULL_NAME; -import static awais.instagrabber.utils.DataBox.KEY_ID; -import static awais.instagrabber.utils.DataBox.KEY_PROFILE_PIC; -import static awais.instagrabber.utils.DataBox.KEY_UID; -import static awais.instagrabber.utils.DataBox.KEY_USERNAME; -import static awais.instagrabber.utils.DataBox.TABLE_COOKIES; public class AccountDataSource { private static final String TAG = AccountDataSource.class.getSimpleName(); private static AccountDataSource INSTANCE; - private final DataBox dataBox; + private final AccountDao accountDao; - private AccountDataSource(@NonNull Context context) { - dataBox = DataBox.getInstance(context); + private AccountDataSource(final AccountDao accountDao) { + this.accountDao = accountDao; } - public static synchronized AccountDataSource getInstance(@NonNull Context context) { + public static AccountDataSource getInstance(@NonNull Context context) { if (INSTANCE == null) { - INSTANCE = new AccountDataSource(context); + synchronized (AccountDataSource.class) { + if (INSTANCE == null) { + final AppDatabase database = AppDatabase.getDatabase(context); + INSTANCE = new AccountDataSource(database.accountDao()); + } + } } return INSTANCE; } @Nullable public final Account getAccount(final String uid) { - Account cookie = null; - try (final SQLiteDatabase db = dataBox.getReadableDatabase(); - final Cursor cursor = db.query(TABLE_COOKIES, - new String[]{ - KEY_ID, - KEY_UID, - KEY_USERNAME, - KEY_COOKIE, - KEY_FULL_NAME, - KEY_PROFILE_PIC - }, - KEY_UID + "=?", - new String[]{uid}, - null, - null, - null)) { - if (cursor != null && cursor.moveToFirst()) - cookie = new Account( - cursor.getInt(cursor.getColumnIndex(KEY_ID)), - cursor.getString(cursor.getColumnIndex(KEY_UID)), - cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), - cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), - cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), - cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) - ); - } - return cookie; + return accountDao.findAccountByUid(uid); } @NonNull public final List getAllAccounts() { - final List cookies = new ArrayList<>(); - try (final SQLiteDatabase db = dataBox.getReadableDatabase(); - final Cursor cursor = db.query(TABLE_COOKIES, - new String[]{ - KEY_ID, - KEY_UID, - KEY_USERNAME, - KEY_COOKIE, - KEY_FULL_NAME, - KEY_PROFILE_PIC - }, - null, - null, - null, - null, - null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - cookies.add(new Account( - cursor.getInt(cursor.getColumnIndex(KEY_ID)), - cursor.getString(cursor.getColumnIndex(KEY_UID)), - cursor.getString(cursor.getColumnIndex(KEY_USERNAME)), - cursor.getString(cursor.getColumnIndex(KEY_COOKIE)), - cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)), - cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC)) - )); - } while (cursor.moveToNext()); - } - } - return cookies; + return accountDao.getAllAccounts(); } - // public final void insertOrUpdateAccount(@NonNull final Account account) { - // insertOrUpdateAccount( - // account.getUid(), - // account.getUsername(), - // account.getCookie(), - // account.getFullName(), - // account.getProfilePic() - // ); - // } - public final void insertOrUpdateAccount(final String uid, final String username, final String cookie, final String fullName, final String profilePicUrl) { - if (TextUtils.isEmpty(uid)) return; - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - db.beginTransaction(); - try { - final ContentValues values = new ContentValues(); - values.put(KEY_USERNAME, username); - values.put(KEY_COOKIE, cookie); - values.put(KEY_UID, uid); - values.put(KEY_FULL_NAME, fullName); - values.put(KEY_PROFILE_PIC, profilePicUrl); - final int rows = db.update(TABLE_COOKIES, values, KEY_UID + "=?", new String[]{uid}); - if (rows != 1) { - db.insert(TABLE_COOKIES, null, values); - } - db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); - } finally { - db.endTransaction(); - } + final Account account = getAccount(uid); + final Account toUpdate = new Account(account == null ? 0 : account.getId(), uid, username, cookie, fullName, profilePicUrl); + if (account != null) { + accountDao.updateAccounts(toUpdate); + return; } + accountDao.insertAccounts(toUpdate); } - public final synchronized void deleteAccount(@NonNull final Account account) { - final String cookieModelUid = account.getUid(); - if (!TextUtils.isEmpty(cookieModelUid)) { - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_COOKIES, KEY_UID + "=? AND " + KEY_USERNAME + "=? AND " + KEY_COOKIE + "=?", - new String[]{cookieModelUid, account.getUsername(), account.getCookie()}); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } finally { - db.endTransaction(); - } - } - } + public final void deleteAccount(@NonNull final Account account) { + accountDao.deleteAccounts(account); } - public final synchronized void deleteAllAccounts() { - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_COOKIES, null, null); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } finally { - db.endTransaction(); - } - } + public final void deleteAllAccounts() { + accountDao.deleteAllAccounts(); } } diff --git a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java index 92a566c6..36798b08 100644 --- a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java +++ b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java @@ -1,180 +1,62 @@ package awais.instagrabber.db.datasources; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.Date; import java.util.List; -import awais.instagrabber.BuildConfig; +import awais.instagrabber.db.AppDatabase; +import awais.instagrabber.db.dao.FavoriteDao; import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.models.enums.FavoriteType; -import awais.instagrabber.utils.DataBox; -import awais.instagrabber.utils.TextUtils; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.DataBox.FAV_COL_DATE_ADDED; -import static awais.instagrabber.utils.DataBox.FAV_COL_DISPLAY_NAME; -import static awais.instagrabber.utils.DataBox.FAV_COL_ID; -import static awais.instagrabber.utils.DataBox.FAV_COL_PIC_URL; -import static awais.instagrabber.utils.DataBox.FAV_COL_QUERY; -import static awais.instagrabber.utils.DataBox.FAV_COL_TYPE; -import static awais.instagrabber.utils.DataBox.TABLE_FAVORITES; -import static awais.instagrabber.utils.Utils.logCollector; public class FavoriteDataSource { private static final String TAG = FavoriteDataSource.class.getSimpleName(); private static FavoriteDataSource INSTANCE; - private final DataBox dataBox; + private final FavoriteDao favoriteDao; - private FavoriteDataSource(@NonNull Context context) { - dataBox = DataBox.getInstance(context); + private FavoriteDataSource(final FavoriteDao favoriteDao) { + this.favoriteDao = favoriteDao; } public static synchronized FavoriteDataSource getInstance(@NonNull Context context) { if (INSTANCE == null) { - INSTANCE = new FavoriteDataSource(context); + synchronized (FavoriteDataSource.class) { + if (INSTANCE == null) { + final AppDatabase database = AppDatabase.getDatabase(context); + INSTANCE = new FavoriteDataSource(database.favoriteDao()); + } + } } return INSTANCE; } + @Nullable public final Favorite getFavorite(@NonNull final String query, @NonNull final FavoriteType type) { - try (final SQLiteDatabase db = dataBox.getReadableDatabase(); - final Cursor cursor = db.query(TABLE_FAVORITES, - new String[]{ - FAV_COL_ID, - FAV_COL_QUERY, - FAV_COL_TYPE, - FAV_COL_DISPLAY_NAME, - FAV_COL_PIC_URL, - FAV_COL_DATE_ADDED - }, - FAV_COL_QUERY + "=?" + " AND " + FAV_COL_TYPE + "=?", - new String[]{ - query, - type.toString() - }, - null, - null, - null)) { - if (cursor != null && cursor.moveToFirst()) { - FavoriteType favoriteType = null; - try { - favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); - } catch (IllegalArgumentException ignored) {} - return new Favorite( - cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), - cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), - favoriteType, - cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), - cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), - new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) - ); - } - } - return null; + return favoriteDao.findFavoriteByQueryAndType(query, type); } @NonNull public final List getAllFavorites() { - final List favorites = new ArrayList<>(); - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - try (final Cursor cursor = db.query(TABLE_FAVORITES, - new String[]{ - FAV_COL_ID, - FAV_COL_QUERY, - FAV_COL_TYPE, - FAV_COL_DISPLAY_NAME, - FAV_COL_PIC_URL, - FAV_COL_DATE_ADDED - }, - null, - null, - null, - null, - null)) { - if (cursor != null && cursor.moveToFirst()) { - db.beginTransaction(); - Favorite tempFav; - do { - FavoriteType type = null; - try { - type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); - } catch (IllegalArgumentException ignored) {} - tempFav = new Favorite( - cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), - cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), - type, - cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), - cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), - new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) - ); - favorites.add(tempFav); - } while (cursor.moveToNext()); - db.endTransaction(); - } - } catch (final Exception e) { - Log.e(TAG, "", e); - } - } - return favorites; + return favoriteDao.getAllFavorites(); } - public final synchronized Favorite insertOrUpdateFavorite(@NonNull final Favorite model) { - final String query = model.getQuery(); - if (!TextUtils.isEmpty(query)) { - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - db.beginTransaction(); - try { - dataBox.insertOrUpdateFavorite(db, model); - db.setTransactionSuccessful(); - } catch (Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "insertOrUpdateFavorite"); - } - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error adding/updating favorite", e); - } - } finally { - db.endTransaction(); - } - } - return getFavorite(model.getQuery(), model.getType()); + public final void insertOrUpdateFavorite(@NonNull final Favorite favorite) { + if (favorite.getId() > 0) { + favoriteDao.updateFavorites(favorite); + return; } - return null; + favoriteDao.insertFavorites(favorite); } - public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { - if (!TextUtils.isEmpty(query)) { - try (final SQLiteDatabase db = dataBox.getWritableDatabase()) { - db.beginTransaction(); - try { - final int rowsDeleted = db.delete(TABLE_FAVORITES, - FAV_COL_QUERY + "=?" + - " AND " + FAV_COL_TYPE + "=?", - new String[]{query, type.toString()}); - - if (rowsDeleted > 0) db.setTransactionSuccessful(); - } catch (final Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite"); - } - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error", e); - } - } finally { - db.endTransaction(); - } - } - } + public final void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { + final Favorite favorite = getFavorite(query, type); + if (favorite == null) return; + favoriteDao.deleteFavorites(favorite); } } diff --git a/app/src/main/java/awais/instagrabber/db/entities/Account.java b/app/src/main/java/awais/instagrabber/db/entities/Account.java index 7f849c6f..57faa716 100644 --- a/app/src/main/java/awais/instagrabber/db/entities/Account.java +++ b/app/src/main/java/awais/instagrabber/db/entities/Account.java @@ -4,32 +4,42 @@ import androidx.annotation.NonNull; import androidx.core.util.ObjectsCompat; import androidx.room.ColumnInfo; import androidx.room.Entity; +import androidx.room.Ignore; import androidx.room.PrimaryKey; +import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.TextUtils; -@Entity(tableName = "cookies") +@Entity(tableName = Account.TABLE_NAME) public class Account { + public final static String TABLE_NAME = "accounts"; + public final static String COL_ID = "id"; + public final static String COL_USERNAME = Constants.EXTRAS_USERNAME; + public final static String COL_COOKIE = "cookie"; + public final static String COL_UID = "uid"; + public final static String COL_FULL_NAME = "full_name"; + public final static String COL_PROFILE_PIC = "profile_pic"; - @PrimaryKey - @ColumnInfo(name = "id") + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = COL_ID) private final int id; - @ColumnInfo(name = "uid") + @ColumnInfo(name = COL_UID) private final String uid; - @ColumnInfo(name = "username") + @ColumnInfo(name = COL_USERNAME) private final String username; - @ColumnInfo(name = "cookie") + @ColumnInfo(name = COL_COOKIE) private final String cookie; - @ColumnInfo(name = "full_name") + @ColumnInfo(name = COL_FULL_NAME) private final String fullName; - @ColumnInfo(name = "profile_pic") + @ColumnInfo(name = COL_PROFILE_PIC) private final String profilePic; + @Ignore private boolean selected; public Account(final int id, diff --git a/app/src/main/java/awais/instagrabber/db/entities/Favorite.java b/app/src/main/java/awais/instagrabber/db/entities/Favorite.java index 7e3bada4..614ba167 100644 --- a/app/src/main/java/awais/instagrabber/db/entities/Favorite.java +++ b/app/src/main/java/awais/instagrabber/db/entities/Favorite.java @@ -10,26 +10,33 @@ import java.util.Date; import awais.instagrabber.models.enums.FavoriteType; -@Entity(tableName = "favorites") +@Entity(tableName = Favorite.TABLE_NAME) public class Favorite { + public final static String TABLE_NAME = "favorites"; + public final static String COL_ID = "id"; + public final static String COL_QUERY = "query_text"; + public final static String COL_TYPE = "type"; + public final static String COL_DISPLAY_NAME = "display_name"; + public final static String COL_PIC_URL = "pic_url"; + public final static String COL_DATE_ADDED = "date_added"; - @PrimaryKey - @ColumnInfo(name = "id") + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = COL_ID) private final int id; - @ColumnInfo(name = "query_text") + @ColumnInfo(name = COL_QUERY) private final String query; - @ColumnInfo(name = "type") + @ColumnInfo(name = COL_TYPE) private final FavoriteType type; - @ColumnInfo(name = "display_name") + @ColumnInfo(name = COL_DISPLAY_NAME) private final String displayName; - @ColumnInfo(name = "pic_url") + @ColumnInfo(name = COL_PIC_URL) private final String picUrl; - @ColumnInfo(name = "date_added") + @ColumnInfo(name = COL_DATE_ADDED) private final Date dateAdded; public Favorite(final int id, diff --git a/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java index 529d848d..bb7c48fd 100644 --- a/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java +++ b/app/src/main/java/awais/instagrabber/db/repositories/AccountRepository.java @@ -21,9 +21,9 @@ public class AccountRepository { this.accountDataSource = accountDataSource; } - public static AccountRepository getInstance(final AppExecutors appExecutors, final AccountDataSource accountDataSource) { + public static AccountRepository getInstance(final AccountDataSource accountDataSource) { if (instance == null) { - instance = new AccountRepository(appExecutors, accountDataSource); + instance = new AccountRepository(AppExecutors.getInstance(), accountDataSource); } return instance; } diff --git a/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java index 3f942b6b..926692f7 100644 --- a/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java +++ b/app/src/main/java/awais/instagrabber/db/repositories/FavoriteRepository.java @@ -20,9 +20,9 @@ public class FavoriteRepository { this.favoriteDataSource = favoriteDataSource; } - public static FavoriteRepository getInstance(final AppExecutors appExecutors, final FavoriteDataSource favoriteDataSource) { + public static FavoriteRepository getInstance(final FavoriteDataSource favoriteDataSource) { if (instance == null) { - instance = new FavoriteRepository(appExecutors, favoriteDataSource); + instance = new FavoriteRepository(AppExecutors.getInstance(), favoriteDataSource); } return instance; } @@ -60,18 +60,14 @@ public class FavoriteRepository { } public void insertOrUpdateFavorite(final Favorite favorite, - final RepositoryCallback callback) { + final RepositoryCallback callback) { // request on the I/O thread appExecutors.diskIO().execute(() -> { - final Favorite updated = favoriteDataSource.insertOrUpdateFavorite(favorite); + favoriteDataSource.insertOrUpdateFavorite(favorite); // notify on the main thread appExecutors.mainThread().execute(() -> { if (callback == null) return; - if (updated == null) { - callback.onDataNotAvailable(); - return; - } - callback.onSuccess(updated); + callback.onSuccess(null); }); }); } diff --git a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java index d1830243..91afa7d1 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java @@ -25,7 +25,6 @@ import awais.instagrabber.db.datasources.AccountDataSource; import awais.instagrabber.db.entities.Account; import awais.instagrabber.db.repositories.AccountRepository; import awais.instagrabber.db.repositories.RepositoryCallback; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; @@ -41,12 +40,12 @@ public class AccountSwitcherDialogFragment extends DialogFragment { private DialogAccountSwitcherBinding binding; public AccountSwitcherDialogFragment() { - accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); } public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) { this.onAddAccountClickListener = onAddAccountClickListener; - accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); } private final AccountSwitcherAdapter.OnAccountClickListener accountClickListener = (model, isCurrent) -> { diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java index 8631add3..190e9a59 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java @@ -32,7 +32,6 @@ import awais.instagrabber.db.datasources.FavoriteDataSource; import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.db.repositories.FavoriteRepository; import awais.instagrabber.db.repositories.RepositoryCallback; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.viewmodels.FavoritesViewModel; @@ -49,7 +48,7 @@ public class FavoritesFragment extends Fragment { @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext())); + favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); } @NonNull @@ -186,9 +185,9 @@ public class FavoritesFragment extends Fragment { result.getSdProfilePic(), model.getDateAdded() ); - favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { + favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { @Override - public void onSuccess(final Favorite result) { + public void onSuccess(final Void result) { updatedList.add(i, updated); try { cyclicBarrier.await(); @@ -225,9 +224,9 @@ public class FavoritesFragment extends Fragment { result.getSdProfilePic(), model.getDateAdded() ); - favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { + favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback() { @Override - public void onSuccess(final Favorite result) { + public void onSuccess(final Void result) { try { cyclicBarrier.await(); } catch (BrokenBarrierException | InterruptedException e) { diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 3400d4f6..26733e12 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -59,7 +59,6 @@ import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.FavoriteType; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -459,8 +458,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE); } hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE); - final FavoriteRepository favoriteRepository = FavoriteRepository - .getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext())); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback() { @Override public void onSuccess(final Favorite result) { @@ -500,9 +498,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe hashtagModel.getName(), null, new Date() - ), new RepositoryCallback() { + ), new RepositoryCallback() { @Override - public void onSuccess(final Favorite result) { + public void onSuccess(final Void result) { hashtagDetailsBinding.favChip.setText(R.string.favorite_short); hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); showSnackbar(getString(R.string.added_to_favs)); diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 0ddde3fb..acccc0b9 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -62,7 +62,6 @@ import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.FavoriteType; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -448,7 +447,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR locationDetailsBinding.locationUrl.setText(TextUtils.getSpannableUrl(url)); } final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(getContext()); - final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource); locationDetailsBinding.favChip.setVisibility(View.VISIBLE); favoriteRepository.getFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback() { @Override @@ -491,9 +490,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR locationModel.getName(), locationModel.getSdProfilePic(), new Date() - ), new RepositoryCallback() { + ), new RepositoryCallback() { @Override - public void onSuccess(final Favorite result) { + public void onSuccess(final Void result) { locationDetailsBinding.favChip.setText(R.string.favorite_short); locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); showSnackbar(getString(R.string.added_to_favs)); diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 2dc2238f..4c12b4e3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -82,7 +82,6 @@ import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -299,9 +298,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe fragmentActivity = (MainActivity) requireActivity(); friendshipService = FriendshipService.getInstance(); storiesService = StoriesService.getInstance(); - final AppExecutors appExecutors = new AppExecutors(); - accountRepository = AccountRepository.getInstance(appExecutors, AccountDataSource.getInstance(getContext())); - favoriteRepository = FavoriteRepository.getInstance(appExecutors, FavoriteDataSource.getInstance(getContext())); + accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); + favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); setHasOptionsMenu(true); } @@ -907,9 +905,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe profileModel.getSdProfilePic(), new Date() ); - favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback() { + favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback() { @Override - public void onSuccess(final Favorite result) { + public void onSuccess(final Void result) { profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); profileDetailsBinding.favProgress.setVisibility(View.GONE); profileDetailsBinding.favCb.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 3c8398ea..5dcdde7a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -33,7 +33,6 @@ import awais.instagrabber.db.repositories.AccountRepository; import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.AccountSwitcherDialogFragment; import awais.instagrabber.repositories.responses.UserInfo; -import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.FlavorTown; @@ -49,7 +48,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment { private final AccountRepository accountRepository; public MorePreferencesFragment() { - accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext())); + accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); } @Override diff --git a/app/src/main/java/awais/instagrabber/utils/AppExecutors.java b/app/src/main/java/awais/instagrabber/utils/AppExecutors.java index ca5b498b..aaf86807 100644 --- a/app/src/main/java/awais/instagrabber/utils/AppExecutors.java +++ b/app/src/main/java/awais/instagrabber/utils/AppExecutors.java @@ -35,37 +35,48 @@ import java.util.concurrent.Executors; */ public class AppExecutors { - // private static final int THREAD_COUNT = 3; + private static final int THREAD_COUNT = 3; + private static final Object LOCK = new Object(); + + private static AppExecutors instance; private final Executor diskIO; - // private final Executor networkIO; + private final Executor networkIO; private final Executor mainThread; private final ListeningExecutorService tasksThread; private AppExecutors(Executor diskIO, - // Executor networkIO, + Executor networkIO, Executor mainThread, ListeningExecutorService tasksThread) { this.diskIO = diskIO; - // this.networkIO = networkIO; + this.networkIO = networkIO; this.mainThread = mainThread; this.tasksThread = tasksThread; } - public AppExecutors() { - this(Executors.newSingleThreadExecutor(), - // Executors.newFixedThreadPool(THREAD_COUNT), - new MainThreadExecutor(), - MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10))); + public static AppExecutors getInstance() { + if (instance == null) { + synchronized (LOCK) { + if (instance == null) { + instance = new AppExecutors(Executors.newSingleThreadExecutor(), + Executors.newFixedThreadPool(THREAD_COUNT), + new MainThreadExecutor(), + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10))); + } + } + } + return instance; } + public Executor diskIO() { return diskIO; } - // public Executor networkIO() { - // return networkIO; - // } + public Executor networkIO() { + return networkIO; + } public ListeningExecutorService tasksThread() { diff --git a/app/src/main/java/awais/instagrabber/utils/CookieUtils.java b/app/src/main/java/awais/instagrabber/utils/CookieUtils.java index 5a1a5e12..78a6998e 100644 --- a/app/src/main/java/awais/instagrabber/utils/CookieUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/CookieUtils.java @@ -63,7 +63,7 @@ public final class CookieUtils { if (cookieStore == null) return; cookieStore.removeAll(); try { - AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)) + AccountRepository.getInstance(AccountDataSource.getInstance(context)) .deleteAllAccounts(callback); } catch (Exception e) { Log.e(TAG, "setupCookies", e); diff --git a/app/src/main/java/awais/instagrabber/utils/DataBox.java b/app/src/main/java/awais/instagrabber/utils/DataBox.java index 3df698f3..23f5d889 100755 --- a/app/src/main/java/awais/instagrabber/utils/DataBox.java +++ b/app/src/main/java/awais/instagrabber/utils/DataBox.java @@ -1,45 +1,19 @@ package awais.instagrabber.utils; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; -import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import awais.instagrabber.db.entities.Favorite; -import awais.instagrabber.models.enums.FavoriteType; - public final class DataBox extends SQLiteOpenHelper { private static final String TAG = "DataBox"; private static DataBox sInstance; private final static int VERSION = 3; - public final static String TABLE_COOKIES = "cookies"; - public final static String TABLE_FAVORITES = "favorites"; - - public final static String KEY_ID = "id"; - public final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; - public final static String KEY_COOKIE = "cookie"; - public final static String KEY_UID = "uid"; - public final static String KEY_FULL_NAME = "full_name"; - public final static String KEY_PROFILE_PIC = "profile_pic"; - - public final static String FAV_COL_ID = "id"; - public final static String FAV_COL_QUERY = "query_text"; - public final static String FAV_COL_TYPE = "type"; - public final static String FAV_COL_DISPLAY_NAME = "display_name"; - public final static String FAV_COL_PIC_URL = "pic_url"; - public final static String FAV_COL_DATE_ADDED = "date_added"; public static synchronized DataBox getInstance(final Context context) { if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); @@ -53,21 +27,6 @@ public final class DataBox extends SQLiteOpenHelper { @Override public void onCreate(@NonNull final SQLiteDatabase db) { Log.i(TAG, "Creating tables..."); - db.execSQL("CREATE TABLE " + TABLE_COOKIES + " (" - + KEY_ID + " INTEGER PRIMARY KEY," - + KEY_UID + " TEXT," - + KEY_USERNAME + " TEXT," - + KEY_COOKIE + " TEXT," - + KEY_FULL_NAME + " TEXT," - + KEY_PROFILE_PIC + " TEXT)"); - // db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); - db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" - + FAV_COL_ID + " INTEGER PRIMARY KEY," - + FAV_COL_QUERY + " TEXT," - + FAV_COL_TYPE + " TEXT," - + FAV_COL_DISPLAY_NAME + " TEXT," - + FAV_COL_PIC_URL + " TEXT," - + FAV_COL_DATE_ADDED + " INTEGER)"); Log.i(TAG, "Tables created!"); } @@ -77,107 +36,8 @@ public final class DataBox extends SQLiteOpenHelper { // switch without break, so that all migrations from a previous version to new are run switch (oldVersion) { case 1: - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); case 2: - final List oldFavorites = backupOldFavorites(db); - // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) - db.execSQL("DROP TABLE " + TABLE_FAVORITES); - db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" - + FAV_COL_ID + " INTEGER PRIMARY KEY," - + FAV_COL_QUERY + " TEXT," - + FAV_COL_TYPE + " TEXT," - + FAV_COL_DISPLAY_NAME + " TEXT," - + FAV_COL_PIC_URL + " TEXT," - + FAV_COL_DATE_ADDED + " INTEGER)"); - // add the old favorites back - for (final Favorite oldFavorite : oldFavorites) { - insertOrUpdateFavorite(db, oldFavorite); - } } Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); } - - public synchronized void insertOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final Favorite model) { - final ContentValues values = new ContentValues(); - values.put(FAV_COL_QUERY, model.getQuery()); - values.put(FAV_COL_TYPE, model.getType().toString()); - values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); - values.put(FAV_COL_PIC_URL, model.getPicUrl()); - values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); - int rows; - if (model.getId() >= 1) { - rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); - } else { - rows = db.update(TABLE_FAVORITES, - values, - FAV_COL_QUERY + "=?" + - " AND " + FAV_COL_TYPE + "=?", - new String[]{model.getQuery(), model.getType().toString()}); - } - if (rows != 1) { - db.insert(TABLE_FAVORITES, null, values); - } - } - - @NonNull - private List backupOldFavorites(@NonNull final SQLiteDatabase db) { - // check if old favorites table had the column query_display - final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display"); - Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); - final List oldModels = new ArrayList<>(); - final String sql = "SELECT " - + "query_text," - + "date_added" - + (queryDisplayExists ? ",query_display" : "") - + " FROM " + TABLE_FAVORITES; - try (final Cursor cursor = db.rawQuery(sql, null)) { - if (cursor != null && cursor.moveToFirst()) { - do { - try { - final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); - final Pair favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText); - if (favoriteTypeQueryPair == null) continue; - final FavoriteType type = favoriteTypeQueryPair.first; - final String query = favoriteTypeQueryPair.second; - oldModels.add(new Favorite( - -1, - query, - type, - queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) - : null, - null, - new Date(cursor.getLong(cursor.getColumnIndex("date_added"))) - )); - } catch (Exception e) { - Log.e(TAG, "onUpgrade", e); - } - } while (cursor.moveToNext()); - } - } catch (Exception e) { - Log.e(TAG, "onUpgrade", e); - } - Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels); - return oldModels; - } - - public boolean checkColumnExists(@NonNull final SQLiteDatabase db, - @NonNull final String tableName, - @NonNull final String columnName) { - boolean exists = false; - try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { - if (cursor.moveToFirst()) { - do { - final String currentColumn = cursor.getString(cursor.getColumnIndex("name")); - if (currentColumn.equals(columnName)) { - exists = true; - } - } while (cursor.moveToNext()); - - } - } catch (Exception ex) { - Log.e(TAG, "checkColumnExists", ex); - } - return exists; - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 5f195562..2d57bd82 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -198,7 +198,7 @@ public final class ExportImportUtils { : favsObject.optString("pic_url"), new Date(favsObject.getLong("d"))); // Log.d(TAG, "importJson: favoriteModel: " + favoriteModel); - FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(context)) + FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)) .insertOrUpdateFavorite(favorite, null); } } @@ -225,7 +225,7 @@ public final class ExportImportUtils { Log.e(TAG, "importAccounts: Error parsing json", e); return; } - AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)) + AccountRepository.getInstance(AccountDataSource.getInstance(context)) .insertOrUpdateAccounts(accounts, null); } @@ -266,9 +266,8 @@ public final class ExportImportUtils { private static void getExportString(@ExportImportFlags final int flags, @NonNull final Context context, final OnExportStringCreatedCallback callback) { - final AppExecutors appExecutors = new AppExecutors(); final Handler innerHandler = new Handler(); - appExecutors.tasksThread().execute(() -> { + AppExecutors.getInstance().tasksThread().execute(() -> { final CountDownLatch responseWaiter = new CountDownLatch(3); try { final JSONObject jsonObject = new JSONObject(); @@ -341,7 +340,7 @@ public final class ExportImportUtils { private static void getFavorites(final Context context, final OnFavoritesJsonLoadedCallback callback) { final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context); - final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource); try { favoriteRepository.getAllFavorites(new RepositoryCallback>() { @Override @@ -379,7 +378,7 @@ public final class ExportImportUtils { } private static void getCookies(final Context context, final OnAccountJsonLoadedCallback callback) { - final AccountRepository accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context)); + final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); accountRepository.getAllAccounts(new RepositoryCallback>() { @Override public void onSuccess(final List accounts) { From cea61eae6cfe65336aba786cfad7a0d54c979141 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 8 Dec 2020 19:39:39 +0900 Subject: [PATCH 4/5] Update export backup logic to use futures instead of CountDownLatch. Change backup file name to use date and time instead of milliseconds. --- .../dialogs/CreateBackupDialogFragment.java | 10 +- .../instagrabber/utils/ExportImportUtils.java | 320 +++++++++--------- 2 files changed, 172 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java index eb0c6a33..f612b72c 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java +++ b/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); diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 2d57bd82..dab09b03 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/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 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,128 +224,177 @@ 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 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); - try { - final JSONObject jsonObject = new JSONObject(); - innerHandler.post(() -> { - if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { - try { - jsonObject.put("settings", getSettings(context)); - } catch (JSONException e) { - Log.e(TAG, "getExportString: ", e); + if (callback == null) return; + try { + final ImmutableList.Builder> 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> allFutures = Futures.allAsList(futures.build()); + Futures.addCallback(allFutures, new FutureCallback>() { + @Override + public void onSuccess(@NullableDecl final List 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(); - }); - 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); - } - responseWaiter.countDown(); - }); - return; + try { + final JSONArray accounts = (JSONArray) result.get(1); + if (accounts != null) { + jsonObject.put("cookies", accounts); + } + } 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); - } - responseWaiter.countDown(); - }); - return; + try { + final JSONArray favorites = (JSONArray) result.get(2); + if (favorites != null) { + jsonObject.put("favs", favorites); + } + } catch (Exception e) { + Log.e(TAG, "error getting favorites: ", e); } - responseWaiter.countDown(); - }); - responseWaiter.await(); - callback.onCreated(jsonObject.toString()); - } catch (final Exception e) { - if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } - callback.onCreated(null); - }); + 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 getSettings(@NonNull final Context context) { final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - final Map allPrefs = sharedPreferences.getAll(); - if (allPrefs == null) { + return AppExecutors.getInstance().tasksThread().submit(() -> { + final Map 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(); - } - 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) { - final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context); - final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource); - try { - favoriteRepository.getAllFavorites(new RepositoryCallback>() { - @Override - public void onSuccess(final List favorites) { - final JSONArray jsonArray = new JSONArray(); - try { - for (final Favorite favorite : favorites) { - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("q", favorite.getQuery()); - jsonObject.put("type", favorite.getType().toString()); - jsonObject.put("s", favorite.getDisplayName()); - jsonObject.put("pic_url", favorite.getPicUrl()); - jsonObject.put("d", favorite.getDateAdded().getTime()); - jsonArray.put(jsonObject); - } - } catch (Exception e) { - Log.e(TAG, "onSuccess: Error creating json array", e); + private static ListenableFuture getFavorites(final Context context) { + final SettableFuture future = SettableFuture.create(); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); + favoriteRepository.getAllFavorites(new RepositoryCallback>() { + @Override + public void onSuccess(final List favorites) { + final JSONArray jsonArray = new JSONArray(); + try { + for (final Favorite favorite : favorites) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("q", favorite.getQuery()); + jsonObject.put("type", favorite.getType().toString()); + jsonObject.put("s", favorite.getDisplayName()); + jsonObject.put("pic_url", favorite.getPicUrl()); + jsonObject.put("d", favorite.getDateAdded().getTime()); + jsonArray.put(jsonObject); + } + } catch (Exception e) { + if (logCollector != null) { + logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error exporting favorites", e); } - callback.onFavoritesJsonLoaded(jsonArray); } + future.set(jsonArray); + } - @Override - public void onDataNotAvailable() { - callback.onFavoritesJsonLoaded(new JSONArray()); - } - }); - } catch (final Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); + @Override + public void onDataNotAvailable() { + future.set(new JSONArray()); } - 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 getCookies(final Context context) { + final SettableFuture future = SettableFuture.create(); final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); accountRepository.getAllAccounts(new RepositoryCallback>() { @Override public void onSuccess(final List accounts) { + final JSONArray jsonArray = new JSONArray(); try { - final JSONArray jsonArray = new JSONArray(); 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) { - 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 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); - } } \ No newline at end of file From 6a5c2171c683003d885a1f651bb00efd80197dcd Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 8 Dec 2020 20:15:52 +0900 Subject: [PATCH 5/5] Generate Room schemas --- app/build.gradle | 6 + .../awais.instagrabber.db.AppDatabase/4.json | 114 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 app/schemas/awais.instagrabber.db.AppDatabase/4.json diff --git a/app/build.gradle b/app/build.gradle index ccc82cfb..5a6611dc 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,12 @@ android { vectorDrawables.useSupportLibrary = true vectorDrawables.generatedDensities = [] + + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] + } + } } compileOptions { diff --git a/app/schemas/awais.instagrabber.db.AppDatabase/4.json b/app/schemas/awais.instagrabber.db.AppDatabase/4.json new file mode 100644 index 00000000..528d203b --- /dev/null +++ b/app/schemas/awais.instagrabber.db.AppDatabase/4.json @@ -0,0 +1,114 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "538d64adaeb8c3a98db9204955932e59", + "entities": [ + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cookie", + "columnName": "cookie", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "profilePic", + "columnName": "profile_pic", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorites", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query_text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "display_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "picUrl", + "columnName": "pic_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '538d64adaeb8c3a98db9204955932e59')" + ] + } +} \ No newline at end of file