From 375278d9985b35126dcd7b75f5e5a275d2a4e367 Mon Sep 17 00:00:00 2001 From: xynngh Date: Sat, 7 Sep 2019 14:39:12 +0400 Subject: [PATCH] Add support for external main DB --- .../yetanothercallblocker/MainActivity.java | 54 ++++++++ .../sia/DatabaseSingleton.java | 6 +- .../sia/SiaConstants.java | 8 ++ .../sia/model/database/AbstractDatabase.java | 115 ++++++++++++++---- .../database/AbstractDatabaseDataSlice.java | 39 ------ .../sia/model/database/CommunityDatabase.java | 83 +++++++++---- .../database/CommunityDatabaseDataSlice.java | 5 - .../sia/model/database/DbManager.java | 48 ++++++++ .../sia/model/database/FeaturedDatabase.java | 6 +- .../database/FeaturedDatabaseDataSlice.java | 5 - .../sia/network/DbDownloader.java | 61 ++++++++++ .../sia/utils/FileUtils.java | 68 +++++++++++ app/src/main/res/layout/activity_main.xml | 48 ++++++++ app/src/main/res/values/strings.xml | 4 + 14 files changed, 448 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/dummydomain/yetanothercallblocker/sia/SiaConstants.java create mode 100644 app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/DbManager.java create mode 100644 app/src/main/java/dummydomain/yetanothercallblocker/sia/network/DbDownloader.java create mode 100644 app/src/main/java/dummydomain/yetanothercallblocker/sia/utils/FileUtils.java diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/MainActivity.java b/app/src/main/java/dummydomain/yetanothercallblocker/MainActivity.java index d5edbb4..2237eec 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/MainActivity.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/MainActivity.java @@ -1,12 +1,19 @@ package dummydomain.yetanothercallblocker; +import android.annotation.SuppressLint; import android.content.Intent; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SwitchCompat; + +import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; +import android.view.View; + +import dummydomain.yetanothercallblocker.sia.DatabaseSingleton; +import dummydomain.yetanothercallblocker.sia.model.database.DbManager; public class MainActivity extends AppCompatActivity { @@ -32,6 +39,20 @@ public class MainActivity extends AppCompatActivity { else Updater.cancelAutoUpdateWorker(); }); + @SuppressLint("StaticFieldLeak") + AsyncTask noDbTask = new AsyncTask() { + @Override + protected Boolean doInBackground(Void... voids) { + return DatabaseSingleton.getCommunityDatabase().isOperational(); + } + + @Override + protected void onPostExecute(Boolean result) { + updateNoDbUi(result ? UpdateUiState.HIDDEN : UpdateUiState.NO_DB); + } + }; + noDbTask.execute(); + PermissionHelper.checkPermissions(this); } @@ -49,8 +70,41 @@ public class MainActivity extends AppCompatActivity { // TODO: handle } + public void onDownloadDbClick(View view) { + // TODO: use service + + updateNoDbUi(UpdateUiState.DOWNLOADING_DB); + + @SuppressLint("StaticFieldLeak") + AsyncTask dlTask = new AsyncTask() { + @Override + protected Boolean doInBackground(Void... voids) { + return DbManager.downloadMainDb() + && DatabaseSingleton.getCommunityDatabase().reload() + && DatabaseSingleton.getFeaturedDatabase().reload(); + } + + @Override + protected void onPostExecute(Boolean result) { + updateNoDbUi(result ? UpdateUiState.HIDDEN : UpdateUiState.ERROR); + } + }; + dlTask.execute(); + } + public void onOpenDebugActivity(MenuItem item) { startActivity(new Intent(this, DebugActivity.class)); } + enum UpdateUiState {HIDDEN, NO_DB, DOWNLOADING_DB, ERROR} + + private void updateNoDbUi(UpdateUiState state) { + findViewById(R.id.noDbText).setVisibility(state == UpdateUiState.NO_DB ? View.VISIBLE : View.GONE); + findViewById(R.id.downloadDbButton).setVisibility(state == UpdateUiState.NO_DB ? View.VISIBLE : View.GONE); + + findViewById(R.id.downloadingDbText).setVisibility(state == UpdateUiState.DOWNLOADING_DB ? View.VISIBLE : View.GONE); + + findViewById(R.id.dbCouldNotBeDownloaded).setVisibility(state == UpdateUiState.ERROR ? View.VISIBLE : View.GONE); + } + } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/DatabaseSingleton.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/DatabaseSingleton.java index 10779d4..b9f8e5e 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/sia/DatabaseSingleton.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/DatabaseSingleton.java @@ -3,10 +3,12 @@ package dummydomain.yetanothercallblocker.sia; import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabase; import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabase; +import static dummydomain.yetanothercallblocker.sia.SiaConstants.*; + public class DatabaseSingleton { - private static final CommunityDatabase COMMUNITY_DATABASE = new CommunityDatabase(); - private static final FeaturedDatabase FEATURED_DATABASE = new FeaturedDatabase(); + private static final CommunityDatabase COMMUNITY_DATABASE = new CommunityDatabase(SIA_PATH_PREFIX, SIA_SECONDARY_PATH_PREFIX); + private static final FeaturedDatabase FEATURED_DATABASE = new FeaturedDatabase(SIA_PATH_PREFIX); public static CommunityDatabase getCommunityDatabase() { return COMMUNITY_DATABASE; diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/SiaConstants.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/SiaConstants.java new file mode 100644 index 0000000..312f46a --- /dev/null +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/SiaConstants.java @@ -0,0 +1,8 @@ +package dummydomain.yetanothercallblocker.sia; + +public interface SiaConstants { + + String SIA_PATH_PREFIX = "sia/"; + String SIA_SECONDARY_PATH_PREFIX = "sia-secondary/"; + +} diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabase.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabase.java index a26b84c..220d7ab 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabase.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabase.java @@ -5,17 +5,22 @@ import android.util.SparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; -import dummydomain.yetanothercallblocker.sia.utils.Utils; +import static dummydomain.yetanothercallblocker.sia.utils.FileUtils.openFile; public abstract class AbstractDatabase, V> { private static final Logger LOG = LoggerFactory.getLogger(AbstractDatabase.class); + protected final String pathPrefix; + + protected boolean useAssets; + protected int baseDatabaseVersion; protected int numberOfItems; @@ -26,39 +31,78 @@ public abstract class AbstractDatabase, V protected boolean loaded; protected boolean failedToLoad; + public AbstractDatabase(String pathPrefix) { + this.pathPrefix = pathPrefix; + } + + public boolean isOperational() { + checkLoaded(); + return loaded; + } + + public boolean reload() { + return load(); + } + protected void checkLoaded() { - if (loaded) return; - if (failedToLoad) throw new IllegalStateException("The DB has failed to load"); + if (loaded || failedToLoad) return; LOG.debug("checkLoaded() loading DB"); - loaded = load(); - failedToLoad = !loaded; + load(); + } + + protected void reset() { + loaded = false; + failedToLoad = false; + + baseDatabaseVersion = 0; + numberOfItems = 0; + + dbRootNode = null; + sliceCache.clear(); } protected boolean load() { LOG.debug("load() loading DB"); - if (loadInfoData() && loadSliceListData()) { - LOG.info("load() loaded DB, baseDatabaseVersion={}, numberOfItems={}", - this.baseDatabaseVersion, this.numberOfItems); + reset(); + + if (load(true)) { + useAssets = true; + loaded = true; + return true; + } else if (load(false)) { + useAssets = false; + loaded = true; return true; } else { LOG.warn("load() failed to load DB"); + failedToLoad = true; return false; } } - protected String getAssetPathPrefix() { - return "sia/"; + protected boolean load(boolean useAssets) { + if (loadInfoData(useAssets) && loadSliceListData(useAssets)) { + LOG.info("load() loaded DB useAssets={}, baseDatabaseVersion={}, numberOfItems={}", + useAssets, this.baseDatabaseVersion, this.numberOfItems); + return true; + } + return false; } - protected abstract String getAssetNamePrefix(); + protected String getPathPrefix() { + return pathPrefix; + } - protected boolean loadInfoData() { - LOG.debug("loadInfoData() started"); + protected abstract String getNamePrefix(); - String fileName = getAssetPathPrefix() + getAssetNamePrefix() + "info.dat"; - try (InputStream is = Utils.getContext().getAssets().open(fileName); + protected boolean loadInfoData(boolean useAssets) { + LOG.debug("loadInfoData() started; useAssets: {}", useAssets); + + String fileName = getPathPrefix() + getNamePrefix() + "info.dat"; + + try (InputStream is = openFile(fileName, useAssets); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) { String headerString = bufferedReader.readLine(); @@ -81,11 +125,11 @@ public abstract class AbstractDatabase, V LOG.debug("loadInfoData() loaded MDI, baseDatabaseVersion={}, numberOfItems={}", this.baseDatabaseVersion, this.numberOfItems); - loadInfoDataAfterLoadedHook(); + loadInfoDataAfterLoadedHook(useAssets); return true; } catch (FileNotFoundException e) { - LOG.error("loadInfoData() the info-file is not found! HAVE YOU COPIED THE ASSETS? (see `Building` in README)", e); + LOG.debug("loadInfoData() the info-file wasn't found"); } catch (Exception e) { LOG.error("loadInfoData() error during info file loading", e); } @@ -93,13 +137,13 @@ public abstract class AbstractDatabase, V return false; } - protected void loadInfoDataAfterLoadedHook() {} + protected void loadInfoDataAfterLoadedHook(boolean useAssets) {} - protected boolean loadSliceListData() { + protected boolean loadSliceListData(boolean useAssets) { LOG.debug("loadSliceListData() started"); - String fileName = getAssetPathPrefix() + getAssetNamePrefix() + "list.dat"; - try (InputStream is = Utils.getContext().getAssets().open(fileName); + String fileName = getPathPrefix() + getNamePrefix() + "list.dat"; + try (InputStream is = openFile(fileName, useAssets); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) { String nodes = bufferedReader.readLine(); @@ -120,6 +164,11 @@ public abstract class AbstractDatabase, V public V getDbItemByNumber(String numberString) { LOG.info("getDbItemByNumber({}) started", numberString); + if (!isOperational()) { + LOG.warn("getDbItemByNumber() request on a non-operational DB; returning null"); + return null; + } + if (numberString == null || numberString.isEmpty()) return null; if (numberString.startsWith("+")) { @@ -134,8 +183,6 @@ public abstract class AbstractDatabase, V return null; } - checkLoaded(); - LOG.debug("getDbItemByNumber() calling internal method for {}", number); return getDbItemByNumberInternal(number); } @@ -156,7 +203,7 @@ public abstract class AbstractDatabase, V LOG.trace("getDataSlice() loading slice with sliceId={}", sliceId); slice = createDbDataSlice(); - slice.loadFromAsset(Utils.getContext(), sliceId); + loadSlice(slice, sliceId); sliceCache.put(sliceId, slice); } else { @@ -165,4 +212,24 @@ public abstract class AbstractDatabase, V return slice; } + protected void loadSlice(T slice, int sliceId) { + LOG.debug("loadSlice() started with sliceId={}", sliceId); + + String fileName = getPathPrefix() + getNamePrefix() + sliceId + ".dat"; + loadSlice(slice, fileName, useAssets); + } + + protected void loadSlice(T slice, String fileName, boolean useAssets) { + LOG.debug("loadSlice() started with fileName={}, useAssets={}", fileName, useAssets); + + try (InputStream is = openFile(fileName, useAssets); + BufferedInputStream stream = new BufferedInputStream(is)) { + + slice.loadFromStream(stream); + } catch (Exception e) { + LOG.error("loadSlice()", e); + } + LOG.trace("loadSlice() finished"); + } + } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabaseDataSlice.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabaseDataSlice.java index d4f91f2..c18d8aa 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabaseDataSlice.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/AbstractDatabaseDataSlice.java @@ -1,14 +1,10 @@ package dummydomain.yetanothercallblocker.sia.model.database; -import android.content.Context; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedInputStream; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import dummydomain.yetanothercallblocker.sia.utils.LittleEndianDataInputStream; @@ -139,41 +135,6 @@ public abstract class AbstractDatabaseDataSlice { return true; } - protected String getAssetPathPrefix() { - return "sia/"; - } - - protected abstract String getAssetNamePrefix(); - - public void loadFromAsset(Context context, int sliceId) { - LOG.debug("loadFromAsset() started with sliceId={}", sliceId); - - String assetName = getAssetPathPrefix() + getAssetNamePrefix() + String.valueOf(sliceId) + ".dat"; - LOG.trace("loadFromAsset() assetName={}", assetName); - - try (InputStream is = context.getAssets().open(assetName); - BufferedInputStream stream = new BufferedInputStream(is)) { - - loadFromStream(stream); - } catch (Exception e) { - LOG.error("loadFromAsset()", e); - } - LOG.trace("loadFromAsset() finished"); - } - - public void loadFromFile(String fileName) { - LOG.debug("loadFromFile({}) started", fileName); - - try (FileInputStream fis = new FileInputStream(fileName); - BufferedInputStream stream = new BufferedInputStream(fis)) { - - loadFromStream(stream); - } catch (Exception e) { - LOG.error("loadFromFile()", e); - } - LOG.trace("loadFromFile() finished"); - } - @Override public String toString() { return "AbstractDatabaseDataSlice{" + diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/CommunityDatabase.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/CommunityDatabase.java index 23f428e..9cfccfc 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/CommunityDatabase.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/CommunityDatabase.java @@ -11,7 +11,6 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; -import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -24,11 +23,16 @@ import java.util.List; import java.util.zip.GZIPInputStream; import dummydomain.yetanothercallblocker.sia.network.WebService; +import dummydomain.yetanothercallblocker.sia.utils.FileUtils; import dummydomain.yetanothercallblocker.sia.utils.Utils; import okhttp3.MediaType; import okhttp3.Response; import okhttp3.ResponseBody; +import static dummydomain.yetanothercallblocker.sia.utils.FileUtils.getDataDir; +import static dummydomain.yetanothercallblocker.sia.utils.FileUtils.getDataDirPath; +import static dummydomain.yetanothercallblocker.sia.utils.FileUtils.openFile; + public class CommunityDatabase extends AbstractDatabase { private enum UpdateResult { @@ -37,13 +41,22 @@ public class CommunityDatabase extends AbstractDatabase secondarySliceCache = new SparseArray<>(); @SuppressLint("UseSparseArrays") // uses null as a special value private SparseArray existingSecondarySliceFiles = new SparseArray<>(); + public CommunityDatabase(String pathPrefix, String secondaryPathPrefix) { + super(pathPrefix); + this.secondaryPathPrefix = secondaryPathPrefix; + } + public int getEffectiveDbVersion() { checkLoaded(); @@ -56,18 +69,27 @@ public class CommunityDatabase extends AbstractDatabase { + public FeaturedDatabase(String pathPrefix) { + super(pathPrefix); + } + @Override - protected String getAssetNamePrefix() { + protected String getNamePrefix() { return "featured_slice_"; } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/FeaturedDatabaseDataSlice.java b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/FeaturedDatabaseDataSlice.java index eb18874..27c3fd9 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/FeaturedDatabaseDataSlice.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/sia/model/database/FeaturedDatabaseDataSlice.java @@ -31,9 +31,4 @@ public class FeaturedDatabaseDataSlice extends AbstractDatabaseDataSlice + + + + + + + + Loading reviews… General settings + Database is not present! For the app to function offline you need to download DB. Press the button below to do it. + Downloading DB… + Database couldn\'t be downloaded Incoming call notifications enabled Block unwanted calls Auto-update enabled + Download database Open debug screen Debug