Add support for external main DB

This commit is contained in:
xynngh 2019-09-07 14:39:12 +04:00
parent f8403c3771
commit 375278d998
14 changed files with 448 additions and 102 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,8 @@
package dummydomain.yetanothercallblocker.sia;
public interface SiaConstants {
String SIA_PATH_PREFIX = "sia/";
String SIA_SECONDARY_PATH_PREFIX = "sia-secondary/";
}

View File

@ -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");
}
}

View File

@ -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{" +

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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_";
}

View File

@ -31,9 +31,4 @@ public class FeaturedDatabaseDataSlice extends AbstractDatabaseDataSlice<Feature
names[index] = stream.readUtf8StringChars(nameLength);
}
@Override
protected String getAssetNamePrefix() {
return "featured_slice_";
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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"

View File

@ -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>