Add support for external main DB
This commit is contained in:
parent
f8403c3771
commit
375278d998
|
@ -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<Void, Void, Boolean> noDbTask = new AsyncTask<Void, Void, Boolean>() {
|
||||
@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<Void, Void, Boolean> dlTask = new AsyncTask<Void, Void, Boolean>() {
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package dummydomain.yetanothercallblocker.sia;
|
||||
|
||||
public interface SiaConstants {
|
||||
|
||||
String SIA_PATH_PREFIX = "sia/";
|
||||
String SIA_SECONDARY_PATH_PREFIX = "sia-secondary/";
|
||||
|
||||
}
|
|
@ -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<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, V
|
|||
return null;
|
||||
}
|
||||
|
||||
checkLoaded();
|
||||
|
||||
LOG.debug("getDbItemByNumber() calling internal method for {}", number);
|
||||
return getDbItemByNumberInternal(number);
|
||||
}
|
||||
|
@ -156,7 +203,7 @@ public abstract class AbstractDatabase<T extends AbstractDatabaseDataSlice<V>, 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<T extends AbstractDatabaseDataSlice<V>, 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<T> {
|
|||
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{" +
|
||||
|
|
|
@ -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<CommunityDatabaseDataSlice, CommunityDatabaseItem> {
|
||||
|
||||
private enum UpdateResult {
|
||||
|
@ -37,13 +41,22 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommunityDatabase.class);
|
||||
|
||||
private int siaAppVersion = 114;
|
||||
private static final int FALLBACK_APP_VERSION = 114;
|
||||
|
||||
private int siaAppVersion = FALLBACK_APP_VERSION;
|
||||
|
||||
protected final String secondaryPathPrefix;
|
||||
|
||||
private SparseArray<CommunityDatabaseDataSlice> secondarySliceCache = new SparseArray<>();
|
||||
|
||||
@SuppressLint("UseSparseArrays") // uses null as a special value
|
||||
private SparseArray<Boolean> 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<CommunityDatabaseDataSli
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getAssetNamePrefix() {
|
||||
protected String getNamePrefix() {
|
||||
return "data_slice_";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean load() {
|
||||
if (!super.load()) return false;
|
||||
protected void reset() {
|
||||
super.reset();
|
||||
|
||||
LOG.debug("load() started");
|
||||
siaAppVersion = FALLBACK_APP_VERSION;
|
||||
secondarySliceCache.clear();
|
||||
existingSecondarySliceFiles.clear();
|
||||
}
|
||||
|
||||
String fileName = getAssetPathPrefix() + "sia_info.dat";
|
||||
try (InputStream is = Utils.getContext().getAssets().open(fileName);
|
||||
@Override
|
||||
protected boolean load(boolean useAssets) {
|
||||
if (!super.load(useAssets)) return false;
|
||||
|
||||
LOG.debug("load() started; useAssets={}", useAssets);
|
||||
|
||||
String fileName = getPathPrefix() + "sia_info.dat";
|
||||
try (InputStream is = openFile(fileName, useAssets);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) {
|
||||
|
||||
if (!"SIA".equals(bufferedReader.readLine())) {
|
||||
|
@ -87,7 +109,7 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void loadInfoDataAfterLoadedHook() {
|
||||
protected void loadInfoDataAfterLoadedHook(boolean useAssets) {
|
||||
int oldDbVersion = Utils.getSettings().getBaseDbVersion();
|
||||
if (baseDatabaseVersion != oldDbVersion) {
|
||||
LOG.info("loadInfoDataAfterLoadedHook() base version changed; resetting secondary DB;" +
|
||||
|
@ -145,7 +167,7 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
String path = getCachedSecondarySliceFilePath(sliceId);
|
||||
if (path != null) {
|
||||
LOG.trace("getSecondaryDataSlice() slice file exists, loading from: {}", path);
|
||||
communityDatabaseDataSlice.loadFromFile(path);
|
||||
loadSlice(communityDatabaseDataSlice, path, false);
|
||||
} else {
|
||||
LOG.trace("getSecondaryDataSlice() slice file doesn't exist");
|
||||
}
|
||||
|
@ -160,30 +182,25 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
String path = getSecondarySliceFilePath(id);
|
||||
Boolean exists = existingSecondarySliceFiles.get(id, null);
|
||||
if (exists == null) {
|
||||
exists = new File(path).exists();
|
||||
exists = new File(getDataDirPath() + path).exists();
|
||||
existingSecondarySliceFiles.put(id, exists);
|
||||
}
|
||||
return exists ? path : null;
|
||||
}
|
||||
|
||||
private String getSecondarySliceFilePath(int id) {
|
||||
File dir = Utils.getContext().getFilesDir();
|
||||
return dir.getAbsolutePath() + "/sia_updates_" + id + ".sia";
|
||||
return getSecondaryDbPathPrefix() + id + ".sia";
|
||||
}
|
||||
|
||||
public void resetSecondaryDatabase() {
|
||||
LOG.debug("resetSecondaryDatabase() started");
|
||||
|
||||
FileFilter fileFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().endsWith(".sia");
|
||||
}
|
||||
};
|
||||
|
||||
for (File file : Utils.getContext().getFilesDir().listFiles(fileFilter)) {
|
||||
if (!file.delete()) {
|
||||
LOG.warn("resetSecondaryDatabase() failed to delete secondary DB file: {}", file.getAbsolutePath());
|
||||
File dir = new File(getDataDir(), getSecondaryDbPathPrefix());
|
||||
if (dir.exists()) {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.delete()) {
|
||||
LOG.warn("resetSecondaryDatabase() failed to delete secondary DB file: {}", file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,9 +211,22 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
LOG.info("resetSecondaryDatabase() secondary DB was reset");
|
||||
}
|
||||
|
||||
protected String getSecondaryDbPathPrefix() {
|
||||
return secondaryPathPrefix;
|
||||
}
|
||||
|
||||
protected void createSecondaryDbDirectory() {
|
||||
FileUtils.createDirectory(getDataDir(), getSecondaryDbPathPrefix());
|
||||
}
|
||||
|
||||
public void updateSecondaryDb() {
|
||||
LOG.info("updateSecondaryDb() started");
|
||||
|
||||
if (!isOperational()) {
|
||||
LOG.warn("updateSecondaryDb() DB is not operational, update aborted");
|
||||
return;
|
||||
}
|
||||
|
||||
long startTimestamp = System.currentTimeMillis();
|
||||
|
||||
boolean updated = false;
|
||||
|
@ -267,6 +297,7 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
LOG.trace("updateSecondaryDbInternal() loading slice from received data");
|
||||
CommunityDatabaseDataSlice slice = new CommunityDatabaseDataSlice();
|
||||
if (slice.loadFromStream(bis)) {
|
||||
createSecondaryDbDirectory();
|
||||
LOG.trace("updateSecondaryDbInternal() distributing slice");
|
||||
updateSecondaryWithSlice(slice);
|
||||
}
|
||||
|
@ -331,11 +362,11 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
if (newSlice.partialClone(dataSlice, sliceId, shortSliceIdToIndexMap, shortSliceIdToIndexToDeleteMap)) {
|
||||
CommunityDatabaseDataSlice sliceFromExistingFile = new CommunityDatabaseDataSlice();
|
||||
if (getCachedSecondarySliceFilePath(sliceId) != null) {
|
||||
sliceFromExistingFile.loadFromFile(filePath);
|
||||
loadSlice(sliceFromExistingFile, filePath, false);
|
||||
}
|
||||
|
||||
try (BufferedOutputStream stream = new BufferedOutputStream(
|
||||
new FileOutputStream(filePath + ".update", false))) {
|
||||
new FileOutputStream(getDataDirPath() + filePath + ".update", false))) {
|
||||
sliceFromExistingFile.writeMerged(newSlice, stream);
|
||||
}
|
||||
|
||||
|
@ -348,7 +379,7 @@ public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSli
|
|||
LOG.debug("updateSecondaryWithSlice() update files created, renaming files");
|
||||
|
||||
for (int sliceId : updatedIndexes) {
|
||||
String filePath = getSecondarySliceFilePath(sliceId);
|
||||
String filePath = getDataDirPath() + getSecondarySliceFilePath(sliceId);
|
||||
|
||||
File updatedFile = new File(filePath + ".update");
|
||||
File oldFile = new File(filePath);
|
||||
|
|
|
@ -141,11 +141,6 @@ public class CommunityDatabaseDataSlice extends AbstractDatabaseDataSlice<Commun
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAssetNamePrefix() {
|
||||
return "data_slice_";
|
||||
}
|
||||
|
||||
public boolean writeMerged(CommunityDatabaseDataSlice newSlice, BufferedOutputStream outputStream) {
|
||||
LOG.debug("writeMerged() started with newSlice={}", newSlice);
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.SiaConstants;
|
||||
import dummydomain.yetanothercallblocker.sia.network.DbDownloader;
|
||||
import dummydomain.yetanothercallblocker.sia.utils.FileUtils;
|
||||
|
||||
public class DbManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DbManager.class);
|
||||
|
||||
public static boolean downloadMainDb() {
|
||||
String url = "https://gitlab.com/xynngh/YetAnotherCallBlocker_data/raw/zip_v1/archives/sia.zip";
|
||||
return downloadMainDb(url);
|
||||
}
|
||||
|
||||
public static boolean downloadMainDb(String url) {
|
||||
LOG.debug("downloadMainDb() started");
|
||||
|
||||
File dataDir = FileUtils.getDataDir();
|
||||
|
||||
String siaDir = SiaConstants.SIA_PATH_PREFIX;
|
||||
String tmpUpdateDir = siaDir.substring(0, siaDir.indexOf('/')) + "-tmp/";
|
||||
String oldDir = siaDir.substring(0, siaDir.indexOf('/')) + "-old/";
|
||||
|
||||
FileUtils.delete(dataDir, tmpUpdateDir);
|
||||
FileUtils.createDirectory(dataDir, tmpUpdateDir);
|
||||
LOG.debug("downloadMainDb() prepared dirs");
|
||||
|
||||
if (DbDownloader.download(url, FileUtils.getDataDirPath() + tmpUpdateDir)) {
|
||||
LOG.debug("downloadMainDb() downloaded and unpacked");
|
||||
new File(dataDir, siaDir).renameTo(new File(dataDir, oldDir));
|
||||
new File(dataDir, tmpUpdateDir).renameTo(new File(dataDir, siaDir));
|
||||
FileUtils.delete(dataDir, oldDir);
|
||||
LOG.debug("downloadMainDb() folders moved");
|
||||
return true;
|
||||
} else {
|
||||
LOG.warn("downloadMainDb() failed downloading");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,8 +2,12 @@ package dummydomain.yetanothercallblocker.sia.model.database;
|
|||
|
||||
public class FeaturedDatabase extends AbstractDatabase<FeaturedDatabaseDataSlice, FeaturedDatabaseItem> {
|
||||
|
||||
public FeaturedDatabase(String pathPrefix) {
|
||||
super(pathPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAssetNamePrefix() {
|
||||
protected String getNamePrefix() {
|
||||
return "featured_slice_";
|
||||
}
|
||||
|
||||
|
|
|
@ -31,9 +31,4 @@ public class FeaturedDatabaseDataSlice extends AbstractDatabaseDataSlice<Feature
|
|||
names[index] = stream.readUtf8StringChars(nameLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAssetNamePrefix() {
|
||||
return "featured_slice_";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package dummydomain.yetanothercallblocker.sia.network;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.utils.FileUtils;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Okio;
|
||||
import okio.Sink;
|
||||
|
||||
public class DbDownloader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DbDownloader.class);
|
||||
|
||||
public static boolean download(String url, String path) {
|
||||
LOG.info("download() started; path: {}", path);
|
||||
|
||||
LOG.debug("download() making a request");
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
try (Response response = new OkHttpClient().newCall(request).execute()) {
|
||||
LOG.debug("download() got response; successful: {}", response.isSuccessful());
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody body = response.body();
|
||||
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(body.byteStream())) {
|
||||
for (ZipEntry zipEntry; (zipEntry = zipInputStream.getNextEntry()) != null; ) {
|
||||
String name = zipEntry.getName();
|
||||
|
||||
if (zipEntry.isDirectory()) {
|
||||
FileUtils.createDirectory(path + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try (Sink out = Okio.sink(new File(path + name))) {
|
||||
Okio.buffer(Okio.source(zipInputStream)).readAll(out);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("download() finished successfully");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
LOG.warn("download() unsuccessful response {}", response.message());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("download()", e);
|
||||
}
|
||||
|
||||
LOG.debug("download() finished unsuccessfully");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package dummydomain.yetanothercallblocker.sia.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
|
||||
|
||||
public static File getDataDir() {
|
||||
return Utils.getContext().getFilesDir();
|
||||
}
|
||||
|
||||
public static String getDataDirPath() {
|
||||
return getDataDir().getAbsolutePath() + "/";
|
||||
}
|
||||
|
||||
public static InputStream openFile(String fileName, boolean asset) throws IOException {
|
||||
if (asset) {
|
||||
return Utils.getContext().getAssets().open(fileName);
|
||||
} else {
|
||||
return new FileInputStream(getDataDirPath() + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public static File createDirectory(String path) {
|
||||
return createDirectory(new File(path));
|
||||
}
|
||||
|
||||
public static File createDirectory(File base, String dir) {
|
||||
return createDirectory(new File(base, dir));
|
||||
}
|
||||
|
||||
public static File createDirectory(File d) {
|
||||
if (!d.exists()) {
|
||||
LOG.debug("createDirectory() creating: {}, result: {}", d.getAbsolutePath(), d.mkdir());
|
||||
}
|
||||
if (!d.isDirectory()) {
|
||||
LOG.error("createDirectory() is not a directory: {}", d.getAbsolutePath());
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
public static void delete(File base, String name) {
|
||||
delete(new File(base, name));
|
||||
}
|
||||
|
||||
public static void delete(File file) {
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
File[] list = file.listFiles();
|
||||
if (list != null) {
|
||||
for (File f : list) {
|
||||
delete(f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,54 @@
|
|||
android:paddingRight="@dimen/item_padding"
|
||||
android:showDividers="middle">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noDbText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="@dimen/text_margin"
|
||||
android:layout_marginLeft="@dimen/text_margin"
|
||||
android:paddingTop="@dimen/item_padding"
|
||||
android:text="@string/no_main_db"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="@color/rateNegative"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/downloadDbButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/text_margin"
|
||||
android:layout_marginBottom="@dimen/text_margin"
|
||||
android:onClick="onDownloadDbClick"
|
||||
android:text="@string/download_main_db"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/downloadingDbText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="@dimen/text_margin"
|
||||
android:layout_marginLeft="@dimen/text_margin"
|
||||
android:paddingTop="@dimen/item_padding"
|
||||
android:text="@string/downloading_db"
|
||||
android:textAlignment="textStart"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dbCouldNotBeDownloaded"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="@dimen/text_margin"
|
||||
android:layout_marginLeft="@dimen/text_margin"
|
||||
android:paddingTop="@dimen/item_padding"
|
||||
android:text="@string/db_could_not_be_downloaded"
|
||||
android:textAlignment="textStart"
|
||||
android:textColor="@color/rateNegative"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/notificationsEnabledSwitch"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -47,10 +47,14 @@
|
|||
<string name="reviews_loading">Loading reviews…</string>
|
||||
|
||||
<string name="general_settings">General settings</string>
|
||||
<string name="no_main_db">Database is not present! For the app to function offline you need to download DB. Press the button below to do it.</string>
|
||||
<string name="downloading_db">Downloading DB…</string>
|
||||
<string name="db_could_not_be_downloaded">Database couldn\'t be downloaded</string>
|
||||
|
||||
<string name="incoming_call_notifications_enabled">Incoming call notifications enabled</string>
|
||||
<string name="block_calls">Block unwanted calls</string>
|
||||
<string name="auto_update_enabled">Auto-update enabled</string>
|
||||
<string name="download_main_db">Download database</string>
|
||||
|
||||
<string name="open_debug_activity">Open debug screen</string>
|
||||
<string name="debug_activity_label">Debug</string>
|
||||
|
|
Loading…
Reference in New Issue