Move DB-related code to a library
This commit is contained in:
parent
be72580311
commit
55b8c57264
|
@ -39,7 +39,7 @@ dependencies {
|
|||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||
//noinspection GradleDependency: 3.12.* is the latest version compatible with Android <5
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
||||
implementation 'commons-codec:commons-codec:1.14' // beware: a version included in Android is used instead
|
||||
implementation 'com.gitlab.xynngh:LibPhoneNumberInfo:096f78cc30'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia;
|
||||
|
||||
public interface Properties {
|
||||
|
||||
int getInt(String key, int defValue);
|
||||
|
||||
void setInt(String key, int value);
|
||||
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia;
|
||||
|
||||
public interface Settings {
|
||||
|
||||
int getBaseDbVersion();
|
||||
|
||||
void setBaseDbVersion(int version);
|
||||
|
||||
int getSecondaryDbVersion();
|
||||
|
||||
void setSecondaryDbVersion(int version);
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia;
|
||||
|
||||
public class SettingsImpl implements Settings {
|
||||
|
||||
private static final String PREF_BASE_DB_VERSION = "baseDbVersion";
|
||||
private static final String PREF_SECONDARY_DB_VERSION = "secondaryDbVersion";
|
||||
|
||||
private final Properties props;
|
||||
|
||||
public SettingsImpl(Properties props) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseDbVersion() {
|
||||
return props.getInt(PREF_BASE_DB_VERSION, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBaseDbVersion(int version) {
|
||||
props.setInt(PREF_BASE_DB_VERSION, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSecondaryDbVersion() {
|
||||
return props.getInt(PREF_SECONDARY_DB_VERSION, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecondaryDbVersion(int version) {
|
||||
props.setInt(PREF_SECONDARY_DB_VERSION, version);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface Storage {
|
||||
|
||||
String getDataDirPath();
|
||||
|
||||
String getCacheDirPath();
|
||||
|
||||
InputStream openFile(String fileName, boolean internal) throws IOException;
|
||||
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model;
|
||||
|
||||
public class CommunityReview {
|
||||
|
||||
public enum Rating {
|
||||
|
||||
UNKNOWN(0),
|
||||
POSITIVE(1),
|
||||
NEGATIVE(2),
|
||||
NEUTRAL(3);
|
||||
|
||||
private int id;
|
||||
|
||||
Rating(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Rating getById(int id) {
|
||||
for (Rating rating : Rating.values()) {
|
||||
if (rating.getId() == id) return rating;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int id;
|
||||
|
||||
private Rating rating;
|
||||
private NumberCategory category;
|
||||
private String author;
|
||||
private String title;
|
||||
private String comment;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Rating getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(Rating rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public NumberCategory getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(NumberCategory category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommunityReview{" +
|
||||
"id=" + id +
|
||||
", rating=" + rating +
|
||||
", category=" + category +
|
||||
", author='" + author + '\'' +
|
||||
", title='" + title + '\'' +
|
||||
", comment='" + comment + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.network.WebService;
|
||||
|
||||
public class CommunityReviewsLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommunityReviewsLoader.class);
|
||||
|
||||
private final WebService webService;
|
||||
private final String country;
|
||||
|
||||
public CommunityReviewsLoader(WebService webService, String country) {
|
||||
this.webService = webService;
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public List<CommunityReview> loadReviews(String number) {
|
||||
LOG.debug("loadReviews({}) started", number);
|
||||
|
||||
if (number.startsWith("+")) {
|
||||
number = number.substring(1);
|
||||
}
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("number", number);
|
||||
params.put("country", country);
|
||||
|
||||
WebService.WSResponse response = webService.callForJson(webService.getGetReviewsUrlPart(), params);
|
||||
|
||||
if (response == null || !response.getSuccessful()) {
|
||||
LOG.warn("loadReviews() response is not successful");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
List<CommunityReview> reviews = new ArrayList<>();
|
||||
|
||||
// response.getJsonObject():
|
||||
// "success": boolean
|
||||
// "nn": String number starting with "+"
|
||||
// "count": int number of items
|
||||
// "items": the following array
|
||||
|
||||
JSONArray items = response.getJsonObject().getJSONArray("items");
|
||||
for (int i = 0; i < items.length(); i++) {
|
||||
JSONObject item = items.getJSONObject(i);
|
||||
|
||||
CommunityReview review = new CommunityReview();
|
||||
review.setId(item.getInt("id"));
|
||||
review.setRating(CommunityReview.Rating.getById(item.getInt("rating")));
|
||||
review.setCategory(NumberCategory.getById(item.getInt("category_id")));
|
||||
review.setAuthor(item.getString("nick"));
|
||||
review.setTitle(item.getString("title"));
|
||||
review.setComment(item.getString("comment"));
|
||||
|
||||
reviews.add(review);
|
||||
}
|
||||
|
||||
LOG.trace("loadReviews() loaded {} reviews", reviews.size());
|
||||
|
||||
return reviews;
|
||||
} catch (JSONException e) {
|
||||
LOG.error("loadReviews()", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model;
|
||||
|
||||
public enum NumberCategory {
|
||||
|
||||
NONE(0),
|
||||
TELEMARKETER(1),
|
||||
DEPT_COLLECTOR(2),
|
||||
SILENT_CALL(3),
|
||||
NUISANCE_CALL(4),
|
||||
UNSOLICITED_CALL(5),
|
||||
CALL_CENTER(6),
|
||||
FAX_MACHINE(7),
|
||||
NON_PROFIT(8),
|
||||
POLITICAL(9),
|
||||
SCAM(10),
|
||||
PRANK(11),
|
||||
SMS(12),
|
||||
SURVEY(13),
|
||||
OTHER(14),
|
||||
FINANCE_SERVICE(15),
|
||||
COMPANY(16),
|
||||
SERVICE(17),
|
||||
ROBOCALL(18),
|
||||
// TODO: check: these are probably not present in the db
|
||||
SAFE_PERSONAL(100),
|
||||
SAFE_COMPANY(101),
|
||||
SAFE_NONPROFIT(102);
|
||||
|
||||
private int id;
|
||||
|
||||
NumberCategory(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static NumberCategory getById(int id) {
|
||||
for (NumberCategory category : values()) {
|
||||
if (category.getId() == id) return category;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
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.Storage;
|
||||
|
||||
public abstract class AbstractDatabase<T extends AbstractDatabaseDataSlice<V>, V> {
|
||||
|
||||
public enum Source {
|
||||
INTERNAL, EXTERNAL, ANY
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractDatabase.class);
|
||||
|
||||
protected final Storage storage;
|
||||
protected final Source source;
|
||||
protected final String pathPrefix;
|
||||
|
||||
protected boolean useInternal;
|
||||
|
||||
protected int baseDatabaseVersion;
|
||||
protected int numberOfItems;
|
||||
|
||||
protected DatabaseDataSliceNode dbRootNode;
|
||||
|
||||
protected SparseArray<T> sliceCache = new SparseArray<>(); // TODO: cache cleaning
|
||||
|
||||
protected boolean loaded;
|
||||
protected boolean failedToLoad;
|
||||
|
||||
public AbstractDatabase(Storage storage, Source source, String pathPrefix) {
|
||||
this.storage = storage;
|
||||
this.source = source;
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
public boolean isOperational() {
|
||||
checkLoaded();
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public int getBaseDbVersion() {
|
||||
checkLoaded();
|
||||
return baseDatabaseVersion;
|
||||
}
|
||||
|
||||
public boolean reload() {
|
||||
return load();
|
||||
}
|
||||
|
||||
protected void checkLoaded() {
|
||||
if (loaded || failedToLoad) return;
|
||||
|
||||
LOG.debug("checkLoaded() loading DB");
|
||||
load();
|
||||
}
|
||||
|
||||
protected void reset() {
|
||||
loaded = false;
|
||||
failedToLoad = false;
|
||||
|
||||
baseDatabaseVersion = 0;
|
||||
numberOfItems = 0;
|
||||
|
||||
dbRootNode = null;
|
||||
sliceCache.clear();
|
||||
}
|
||||
|
||||
protected boolean load() {
|
||||
LOG.debug("load() loading DB");
|
||||
|
||||
reset();
|
||||
|
||||
if ((source == Source.ANY || source == Source.INTERNAL) && load(true)) {
|
||||
useInternal = true;
|
||||
loaded = true;
|
||||
return true;
|
||||
} else if ((source == Source.ANY || source == Source.EXTERNAL) && load(false)) {
|
||||
useInternal = false;
|
||||
loaded = true;
|
||||
return true;
|
||||
} else {
|
||||
LOG.warn("load() failed to load DB");
|
||||
failedToLoad = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean load(boolean useInternal) {
|
||||
if (loadInfoData(useInternal) && loadSliceListData(useInternal)) {
|
||||
LOG.info("load() loaded DB useInternal={}, baseDatabaseVersion={}, numberOfItems={}",
|
||||
useInternal, this.baseDatabaseVersion, this.numberOfItems);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String getPathPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
protected abstract String getNamePrefix();
|
||||
|
||||
protected boolean loadInfoData(boolean useInternal) {
|
||||
LOG.debug("loadInfoData() started; useInternal: {}", useInternal);
|
||||
|
||||
String fileName = getPathPrefix() + getNamePrefix() + "info.dat";
|
||||
|
||||
try (InputStream is = storage.openFile(fileName, useInternal);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) {
|
||||
|
||||
String headerString = bufferedReader.readLine();
|
||||
if (!"YACBSIAI".equals(headerString) && !"MDI".equals(headerString)) {
|
||||
LOG.debug("loadInfoData() incorrect header: {}", headerString);
|
||||
return false;
|
||||
}
|
||||
|
||||
// NB: SIA ignores Integer.parseInt() exceptions and falls back to 0
|
||||
|
||||
String dbVersionString = bufferedReader.readLine();
|
||||
int dbVersion = Integer.parseInt(dbVersionString);
|
||||
|
||||
String numberOfItemsString = bufferedReader.readLine();
|
||||
int numberOfItems = Integer.parseInt(numberOfItemsString);
|
||||
|
||||
this.baseDatabaseVersion = dbVersion;
|
||||
this.numberOfItems = numberOfItems;
|
||||
|
||||
LOG.debug("loadInfoData() loaded MDI, baseDatabaseVersion={}, numberOfItems={}",
|
||||
this.baseDatabaseVersion, this.numberOfItems);
|
||||
|
||||
loadInfoDataAfterLoadedHook(useInternal);
|
||||
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.debug("loadInfoData() the info-file wasn't found");
|
||||
} catch (Exception e) {
|
||||
LOG.error("loadInfoData() error during info file loading", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void loadInfoDataAfterLoadedHook(boolean useInternal) {}
|
||||
|
||||
protected boolean loadSliceListData(boolean useInternal) {
|
||||
LOG.debug("loadSliceListData() started");
|
||||
|
||||
String fileName = getPathPrefix() + getNamePrefix() + "list.dat";
|
||||
try (InputStream is = storage.openFile(fileName, useInternal);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) {
|
||||
String nodes = bufferedReader.readLine();
|
||||
|
||||
dbRootNode = new DatabaseDataSliceNode();
|
||||
dbRootNode.init(nodes, 0);
|
||||
|
||||
LOG.info("loadSliceListData() loaded slice list data");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("loadSliceListData() error", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract V getDbItemByNumberInternal(long number);
|
||||
|
||||
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("+")) {
|
||||
numberString = numberString.substring(1);
|
||||
}
|
||||
|
||||
long number;
|
||||
try {
|
||||
number = Long.parseLong(numberString);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warn("getDbItemByNumber() couldn't parse number: " + numberString, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.debug("getDbItemByNumber() calling internal method for {}", number);
|
||||
return getDbItemByNumberInternal(number);
|
||||
}
|
||||
|
||||
protected abstract T createDbDataSlice();
|
||||
|
||||
protected T getDataSlice(long number) {
|
||||
LOG.debug("getDataSlice({}) started", number);
|
||||
|
||||
if (number <= 0) return null;
|
||||
|
||||
int sliceId = dbRootNode.getSliceId(0, String.valueOf(number));
|
||||
LOG.trace("getDataSlice() sliceId={}", sliceId);
|
||||
if (sliceId <= 0) return null;
|
||||
|
||||
T slice = sliceCache.get(sliceId);
|
||||
if (slice == null) {
|
||||
LOG.trace("getDataSlice() loading slice with sliceId={}", sliceId);
|
||||
|
||||
slice = createDbDataSlice();
|
||||
loadSlice(slice, sliceId);
|
||||
|
||||
sliceCache.put(sliceId, slice);
|
||||
} else {
|
||||
LOG.trace("getDataSlice() found slice in cache");
|
||||
}
|
||||
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, useInternal);
|
||||
}
|
||||
|
||||
protected void loadSlice(T slice, String fileName, boolean useInternal) {
|
||||
LOG.debug("loadSlice() started with fileName={}, useInternal={}", fileName, useInternal);
|
||||
|
||||
try (InputStream is = storage.openFile(fileName, useInternal);
|
||||
BufferedInputStream stream = new BufferedInputStream(is)) {
|
||||
|
||||
slice.loadFromStream(stream);
|
||||
} catch (Exception e) {
|
||||
LOG.error("loadSlice()", e);
|
||||
}
|
||||
LOG.trace("loadSlice() finished");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.utils.LittleEndianDataInputStream;
|
||||
|
||||
public abstract class AbstractDatabaseDataSlice<T> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractDatabaseDataSlice.class);
|
||||
|
||||
protected int dbVersion;
|
||||
|
||||
protected int numberOfItems;
|
||||
|
||||
protected long[] numbers;
|
||||
|
||||
protected long lastAccessTimestamp;
|
||||
|
||||
public int getDbVersion() {
|
||||
return this.dbVersion;
|
||||
}
|
||||
|
||||
public int getNumberOfItems() {
|
||||
return this.numberOfItems;
|
||||
}
|
||||
|
||||
public long getLastAccessTimestamp() {
|
||||
return this.lastAccessTimestamp;
|
||||
}
|
||||
|
||||
protected int indexOf(long number) {
|
||||
if (numberOfItems > 0) {
|
||||
return Arrays.binarySearch(numbers, number);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public T getDbItemByNumber(long number) {
|
||||
// LOG.debug("getDbItemByNumber({}) started", number);
|
||||
|
||||
lastAccessTimestamp = System.currentTimeMillis();
|
||||
|
||||
int index = indexOf(number);
|
||||
// LOG.trace("getDbItemByNumber() index={}", index);
|
||||
if (index < 0) return null;
|
||||
|
||||
return getDbItemByNumberInternal(number, index);
|
||||
}
|
||||
|
||||
protected abstract T getDbItemByNumberInternal(long number, int index);
|
||||
|
||||
protected void loadFromStreamCheckHeader(String header) {}
|
||||
|
||||
protected void loadFromStreamReadPostHeaderData(LittleEndianDataInputStream stream) throws IOException {}
|
||||
|
||||
protected void loadFromStreamReadPostVersionData(LittleEndianDataInputStream stream) throws IOException {}
|
||||
|
||||
protected void loadFromStreamInitFields() {}
|
||||
|
||||
protected void loadFromStreamLoadFields(int index, LittleEndianDataInputStream stream) throws IOException {}
|
||||
|
||||
protected void loadFromStreamLoadExtras(LittleEndianDataInputStream stream) throws IOException {
|
||||
int numberOfExtras = stream.readInt();
|
||||
if (numberOfExtras != 0) {
|
||||
throw new IllegalStateException("Number of extras is not 0: " + numberOfExtras);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean loadFromStream(BufferedInputStream inputStream) {
|
||||
LOG.debug("loadFromStream() started");
|
||||
|
||||
try {
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
this.dbVersion = 0;
|
||||
|
||||
LittleEndianDataInputStream stream = new LittleEndianDataInputStream(inputStream);
|
||||
|
||||
LOG.trace("loadFromStream() reading header");
|
||||
String headerString = stream.readUtf8StringChars(4);
|
||||
loadFromStreamCheckHeader(headerString);
|
||||
|
||||
LOG.trace("loadFromStream() reading post header data");
|
||||
loadFromStreamReadPostHeaderData(stream);
|
||||
|
||||
LOG.trace("loadFromStream() reading DB version");
|
||||
this.dbVersion = stream.readInt();
|
||||
LOG.trace("loadFromStream() DB version is {}", dbVersion);
|
||||
|
||||
LOG.trace("loadFromStream() reading post version data");
|
||||
loadFromStreamReadPostVersionData(stream);
|
||||
|
||||
LOG.trace("loadFromStream() reading number of items");
|
||||
this.numberOfItems = stream.readInt();
|
||||
LOG.trace("loadFromStream() number of items is {}", numberOfItems);
|
||||
|
||||
this.numbers = new long[this.numberOfItems];
|
||||
|
||||
loadFromStreamInitFields();
|
||||
|
||||
LOG.trace("loadFromStream() reading fields");
|
||||
for (int i = 0; i < this.numberOfItems; i++) {
|
||||
this.numbers[i] = stream.readLong();
|
||||
loadFromStreamLoadFields(i, stream);
|
||||
}
|
||||
LOG.trace("loadFromStream() finished reading fields");
|
||||
|
||||
LOG.trace("loadFromStream() reading CP");
|
||||
String dividerString = stream.readUtf8StringChars(2);
|
||||
if (!"CP".equalsIgnoreCase(dividerString)) {
|
||||
throw new IllegalStateException("CP not found. Found instead: " + dividerString);
|
||||
}
|
||||
|
||||
LOG.trace("loadFromStream() reading extras");
|
||||
loadFromStreamLoadExtras(stream);
|
||||
|
||||
LOG.trace("loadFromStream() reading endmark");
|
||||
String endmarkString = stream.readUtf8StringChars(6);
|
||||
if (!"YABEND".equalsIgnoreCase(endmarkString) && !"MTZEND".equalsIgnoreCase(endmarkString)) {
|
||||
throw new IllegalStateException("Endmark not found. Found instead: " + endmarkString);
|
||||
}
|
||||
|
||||
LOG.debug("loadFromStream() loaded slice with {} items in {} ms",
|
||||
numberOfItems, System.currentTimeMillis() - currentTimeMillis);
|
||||
} catch (Exception e) {
|
||||
LOG.error("loadFromStream()", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AbstractDatabaseDataSlice{" +
|
||||
"dbVersion=" + dbVersion +
|
||||
", numberOfItems=" + numberOfItems +
|
||||
", lastAccessTimestamp=" + lastAccessTimestamp +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,416 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.Settings;
|
||||
import dummydomain.yetanothercallblocker.sia.Storage;
|
||||
import dummydomain.yetanothercallblocker.sia.network.WebService;
|
||||
import dummydomain.yetanothercallblocker.sia.utils.FileUtils;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class CommunityDatabase extends AbstractDatabase<CommunityDatabaseDataSlice, CommunityDatabaseItem> {
|
||||
|
||||
private enum UpdateResult {
|
||||
UPDATED, NO_UPDATES, OUTDATED_APP, BAD_SECONDARY, UNKNOWN_ERROR
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommunityDatabase.class);
|
||||
|
||||
private static final int FALLBACK_APP_VERSION = 114;
|
||||
|
||||
private int siaAppVersion = FALLBACK_APP_VERSION;
|
||||
|
||||
private final String secondaryPathPrefix;
|
||||
private final Settings settings;
|
||||
private final WebService webService;
|
||||
|
||||
private SparseArray<CommunityDatabaseDataSlice> secondarySliceCache = new SparseArray<>();
|
||||
|
||||
@SuppressLint("UseSparseArrays") // uses null as a special value
|
||||
private SparseArray<Boolean> existingSecondarySliceFiles = new SparseArray<>();
|
||||
|
||||
public CommunityDatabase(Storage storage, Source source,
|
||||
String pathPrefix, String secondaryPathPrefix,
|
||||
Settings settings, WebService webService) {
|
||||
super(storage, source, pathPrefix);
|
||||
this.secondaryPathPrefix = secondaryPathPrefix;
|
||||
this.settings = settings;
|
||||
this.webService = webService;
|
||||
}
|
||||
|
||||
public int getEffectiveDbVersion() {
|
||||
checkLoaded();
|
||||
|
||||
int secondaryDbVersion = settings.getSecondaryDbVersion();
|
||||
return secondaryDbVersion > 0 ? secondaryDbVersion : baseDatabaseVersion;
|
||||
}
|
||||
|
||||
public int getSiaAppVersion() {
|
||||
return siaAppVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNamePrefix() {
|
||||
return "data_slice_";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void reset() {
|
||||
super.reset();
|
||||
|
||||
siaAppVersion = FALLBACK_APP_VERSION;
|
||||
secondarySliceCache.clear();
|
||||
existingSecondarySliceFiles.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean load(boolean useInternal) {
|
||||
if (!super.load(useInternal)) return false;
|
||||
|
||||
LOG.debug("load() started; useInternal={}", useInternal);
|
||||
|
||||
String fileName = getPathPrefix() + "sia_info.dat";
|
||||
try (InputStream is = storage.openFile(fileName, useInternal);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) {
|
||||
|
||||
if (!"SIA".equals(bufferedReader.readLine())) {
|
||||
LOG.debug("load() incorrect header");
|
||||
return false;
|
||||
}
|
||||
|
||||
String appVersionString = bufferedReader.readLine();
|
||||
siaAppVersion = Integer.parseInt(appVersionString);
|
||||
|
||||
LOG.debug("load() loaded extra info; siaAppVersion={}", this.siaAppVersion);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("load() failed to load extra info", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadInfoDataAfterLoadedHook(boolean useInternal) {
|
||||
int oldDbVersion = settings.getBaseDbVersion();
|
||||
if (baseDatabaseVersion != oldDbVersion) {
|
||||
LOG.info("loadInfoDataAfterLoadedHook() base version changed; resetting secondary DB;" +
|
||||
" oldDbVersion={}, baseDatabaseVersion={}", oldDbVersion, baseDatabaseVersion);
|
||||
resetSecondaryDatabase();
|
||||
settings.setBaseDbVersion(baseDatabaseVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommunityDatabaseItem getDbItemByNumberInternal(long number) {
|
||||
LOG.debug("getDbItemByNumberInternal({}) started", number);
|
||||
|
||||
CommunityDatabaseDataSlice secondarySlice = getSecondaryDataSlice(number);
|
||||
|
||||
CommunityDatabaseItem communityDatabaseItem = secondarySlice != null
|
||||
? secondarySlice.getDbItemByNumber(number) : null;
|
||||
|
||||
if (communityDatabaseItem == null) {
|
||||
LOG.trace("getDbItemByNumberInternal() not found in secondary DB");
|
||||
CommunityDatabaseDataSlice baseSlice = getDataSlice(number);
|
||||
communityDatabaseItem = baseSlice != null ? baseSlice.getDbItemByNumber(number) : null;
|
||||
}
|
||||
|
||||
LOG.trace("getDbItemByNumberInternal() communityDatabaseItem={}", communityDatabaseItem);
|
||||
|
||||
if (communityDatabaseItem != null && !communityDatabaseItem.hasRatings()) {
|
||||
communityDatabaseItem = null;
|
||||
}
|
||||
|
||||
return communityDatabaseItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommunityDatabaseDataSlice createDbDataSlice() {
|
||||
return new CommunityDatabaseDataSlice();
|
||||
}
|
||||
|
||||
private CommunityDatabaseDataSlice getSecondaryDataSlice(long number) {
|
||||
LOG.debug("getSecondaryDataSlice({}) started", number);
|
||||
|
||||
if (number <= 0) return null;
|
||||
|
||||
String numberString = String.valueOf(number);
|
||||
if (numberString.length() < 2) return null;
|
||||
|
||||
int sliceId = Integer.parseInt(numberString.substring(0, 2));
|
||||
LOG.trace("getSecondaryDataSlice() sliceId={}", sliceId);
|
||||
|
||||
CommunityDatabaseDataSlice communityDatabaseDataSlice = secondarySliceCache.get(sliceId);
|
||||
if (communityDatabaseDataSlice == null) {
|
||||
LOG.trace("getSecondaryDataSlice() trying to load slice with sliceId={}", sliceId);
|
||||
|
||||
communityDatabaseDataSlice = new CommunityDatabaseDataSlice();
|
||||
String path = getCachedSecondarySliceFilePath(sliceId);
|
||||
if (path != null) {
|
||||
LOG.trace("getSecondaryDataSlice() slice file exists, loading from: {}", path);
|
||||
loadSlice(communityDatabaseDataSlice, path, false);
|
||||
} else {
|
||||
LOG.trace("getSecondaryDataSlice() slice file doesn't exist");
|
||||
}
|
||||
secondarySliceCache.put(sliceId, communityDatabaseDataSlice);
|
||||
} else {
|
||||
LOG.trace("getSecondaryDataSlice() found slice in cache");
|
||||
}
|
||||
return communityDatabaseDataSlice;
|
||||
}
|
||||
|
||||
@Nullable private String getCachedSecondarySliceFilePath(int id) {
|
||||
String path = getSecondarySliceFilePath(id);
|
||||
Boolean exists = existingSecondarySliceFiles.get(id, null);
|
||||
if (exists == null) {
|
||||
exists = new File(storage.getDataDirPath() + path).exists();
|
||||
existingSecondarySliceFiles.put(id, exists);
|
||||
}
|
||||
return exists ? path : null;
|
||||
}
|
||||
|
||||
private String getSecondarySliceFilePath(int id) {
|
||||
return getSecondaryDbPathPrefix() + id + ".sia";
|
||||
}
|
||||
|
||||
public void resetSecondaryDatabase() {
|
||||
LOG.debug("resetSecondaryDatabase() started");
|
||||
|
||||
File dir = new File(storage.getDataDirPath(), getSecondaryDbPathPrefix());
|
||||
if (dir.exists()) {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (!file.delete()) {
|
||||
LOG.warn("resetSecondaryDatabase() failed to delete secondary DB file: {}", file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secondarySliceCache.clear();
|
||||
existingSecondarySliceFiles.clear();
|
||||
settings.setSecondaryDbVersion(0);
|
||||
|
||||
LOG.info("resetSecondaryDatabase() secondary DB was reset");
|
||||
}
|
||||
|
||||
private String getSecondaryDbPathPrefix() {
|
||||
return secondaryPathPrefix;
|
||||
}
|
||||
|
||||
private void createSecondaryDbDirectory() {
|
||||
FileUtils.createDirectory(storage.getDataDirPath(), getSecondaryDbPathPrefix());
|
||||
}
|
||||
|
||||
public boolean updateSecondaryDb() {
|
||||
LOG.info("updateSecondaryDb() started");
|
||||
|
||||
if (!isOperational()) {
|
||||
LOG.warn("updateSecondaryDb() DB is not operational, update aborted");
|
||||
return false;
|
||||
}
|
||||
|
||||
long startTimestamp = System.currentTimeMillis();
|
||||
|
||||
boolean updated = false;
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
UpdateResult result = updateSecondaryDbInternal();
|
||||
LOG.debug("updateSecondaryDb() internal update result: {}", result);
|
||||
if (result == UpdateResult.UPDATED) {
|
||||
updated = true;
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("updateSecondaryDb DB version after update: {}", getEffectiveDbVersion());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
LOG.info("updateSecondaryDb() new DB version: {}", getEffectiveDbVersion());
|
||||
}
|
||||
|
||||
LOG.info("updateSecondaryDb() finished in {} ms", System.currentTimeMillis() - startTimestamp);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private UpdateResult updateSecondaryDbInternal() {
|
||||
LOG.debug("updateSecondaryDbInternal() started");
|
||||
|
||||
long startTimestamp = System.currentTimeMillis();
|
||||
|
||||
int effectiveDbVersion = getEffectiveDbVersion();
|
||||
LOG.debug("updateSecondaryDbInternal() effectiveDbVersion={}", effectiveDbVersion);
|
||||
|
||||
String dbVersionParam = "_dbVer=" + effectiveDbVersion;
|
||||
String urlPath = webService.getGetDatabaseUrlPart() + "/cached?" + dbVersionParam;
|
||||
|
||||
try {
|
||||
Response response = webService.call(urlPath, new HashMap<>());
|
||||
if (response != null) {
|
||||
ResponseBody body = response.body();
|
||||
MediaType contentType = body.contentType();
|
||||
LOG.debug("updateSecondaryDbInternal() response contentType={}", contentType);
|
||||
|
||||
if (contentType != null && "application".equals(contentType.type())) {
|
||||
LOG.trace("updateSecondaryDbInternal() saving response data to file");
|
||||
|
||||
File tempFile = File.createTempFile("sia", "database",
|
||||
new File(storage.getCacheDirPath()));
|
||||
|
||||
int totalRead = 0;
|
||||
try (InputStream in = body.byteStream();
|
||||
OutputStream out = new FileOutputStream(tempFile)) {
|
||||
byte[] buff = new byte[10240];
|
||||
|
||||
while (true) {
|
||||
int read = in.read(buff);
|
||||
if (read == -1) {
|
||||
break;
|
||||
}
|
||||
out.write(buff, 0, read);
|
||||
totalRead += read;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.trace("updateSecondaryDbInternal() finished saving response data to file; totalRead={}", totalRead);
|
||||
|
||||
if (totalRead > 0) {
|
||||
try (FileInputStream fis = new FileInputStream(tempFile);
|
||||
BufferedInputStream bis = new BufferedInputStream(new GZIPInputStream(fis))) {
|
||||
LOG.trace("updateSecondaryDbInternal() loading slice from received data");
|
||||
CommunityDatabaseDataSlice slice = new CommunityDatabaseDataSlice();
|
||||
if (slice.loadFromStream(bis)) {
|
||||
createSecondaryDbDirectory();
|
||||
LOG.trace("updateSecondaryDbInternal() distributing slice");
|
||||
updateSecondaryWithSlice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.trace("updateSecondaryDbInternal() finished processing slice");
|
||||
|
||||
if (!tempFile.delete()) {
|
||||
LOG.warn("updateSecondaryDbInternal() failed to delete tempFile {}", tempFile);
|
||||
}
|
||||
|
||||
LOG.debug("updateSecondaryDbInternal() updated performed successfully in {} ms",
|
||||
System.currentTimeMillis() - startTimestamp);
|
||||
|
||||
return UpdateResult.UPDATED;
|
||||
} else {
|
||||
String responseString = body.string();
|
||||
LOG.debug("updateSecondaryDbInternal() responseString={}, elapsed time: {} ms",
|
||||
responseString, System.currentTimeMillis() - startTimestamp);
|
||||
|
||||
responseString = responseString.replaceAll("\n", "");
|
||||
switch (responseString) {
|
||||
case "OAP":
|
||||
LOG.trace("updateSecondaryDbInternal() server reported outdated app");
|
||||
// outdated app
|
||||
return UpdateResult.OUTDATED_APP;
|
||||
|
||||
case "NC":
|
||||
LOG.trace("updateSecondaryDbInternal() server reported no updates");
|
||||
// "No checkAndUpdate available" - probably "up to date"
|
||||
return UpdateResult.NO_UPDATES;
|
||||
|
||||
case "OOD":
|
||||
LOG.trace("updateSecondaryDbInternal() server suggests to reset secondary DB");
|
||||
// remove secondary DB and retry
|
||||
return UpdateResult.BAD_SECONDARY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.warn("updateSecondaryDbInternal() response is null");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("updateSecondaryDbInternal() IOE", e);
|
||||
} catch (Exception e) {
|
||||
LOG.error("updateSecondaryDbInternal()", e);
|
||||
}
|
||||
return UpdateResult.UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
private boolean updateSecondaryWithSlice(CommunityDatabaseDataSlice dataSlice) {
|
||||
LOG.debug("updateSecondaryWithSlice() started");
|
||||
LOG.trace("updateSecondaryWithSlice() dataSlice={}", dataSlice);
|
||||
|
||||
long startTimestamp = System.currentTimeMillis();
|
||||
try {
|
||||
SparseArray<List<Integer>> shortSliceIdToIndexMap = new SparseArray<>();
|
||||
SparseArray<List<Integer>> shortSliceIdToIndexToDeleteMap = new SparseArray<>();
|
||||
dataSlice.fillIndexMaps(shortSliceIdToIndexMap, shortSliceIdToIndexToDeleteMap);
|
||||
|
||||
ArrayList<Integer> updatedIndexes = new ArrayList<>();
|
||||
for (int sliceId = 0; sliceId <= 99; sliceId++) {
|
||||
String filePath = getSecondarySliceFilePath(sliceId);
|
||||
|
||||
CommunityDatabaseDataSlice newSlice = new CommunityDatabaseDataSlice();
|
||||
if (newSlice.partialClone(dataSlice, sliceId, shortSliceIdToIndexMap, shortSliceIdToIndexToDeleteMap)) {
|
||||
CommunityDatabaseDataSlice sliceFromExistingFile = new CommunityDatabaseDataSlice();
|
||||
if (getCachedSecondarySliceFilePath(sliceId) != null) {
|
||||
loadSlice(sliceFromExistingFile, filePath, false);
|
||||
}
|
||||
|
||||
try (BufferedOutputStream stream = new BufferedOutputStream(
|
||||
new FileOutputStream(storage.getDataDirPath() + filePath + ".update", false))) {
|
||||
sliceFromExistingFile.writeMerged(newSlice, stream);
|
||||
}
|
||||
|
||||
updatedIndexes.add(sliceId);
|
||||
|
||||
LOG.debug("updateSecondaryWithSlice() added {} items to sliceId={}", newSlice.getNumberOfItems(), sliceId);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("updateSecondaryWithSlice() update files created, renaming files");
|
||||
|
||||
for (int sliceId : updatedIndexes) {
|
||||
String filePath = storage.getDataDirPath() + getSecondarySliceFilePath(sliceId);
|
||||
|
||||
File updatedFile = new File(filePath + ".update");
|
||||
File oldFile = new File(filePath);
|
||||
if (oldFile.exists() && !oldFile.delete()) {
|
||||
throw new IllegalStateException("Can't delete " + filePath);
|
||||
}
|
||||
if (!updatedFile.renameTo(oldFile)) {
|
||||
throw new IllegalStateException("Can't replace slice " + updatedFile);
|
||||
}
|
||||
}
|
||||
|
||||
settings.setSecondaryDbVersion(dataSlice.getDbVersion());
|
||||
secondarySliceCache.clear();
|
||||
existingSecondarySliceFiles.clear();
|
||||
|
||||
LOG.debug("updateSecondaryWithSlice() finished in {} ms", System.currentTimeMillis() - startTimestamp);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("updateSecondaryWithSlice()", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,239 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.utils.LittleEndianDataInputStream;
|
||||
import dummydomain.yetanothercallblocker.sia.utils.LittleEndianDataOutputStream;
|
||||
|
||||
public class CommunityDatabaseDataSlice extends AbstractDatabaseDataSlice<CommunityDatabaseItem> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommunityDatabaseDataSlice.class);
|
||||
|
||||
private byte[] positiveRatingsCounts;
|
||||
private byte[] negativeRatingsCounts;
|
||||
private byte[] neutralRatingsCounts;
|
||||
private byte[] unknownData;
|
||||
private byte[] categories;
|
||||
|
||||
private long[] numbersToDelete;
|
||||
|
||||
public void fillIndexMaps(SparseArray<List<Integer>> shortSliceIdToIndexMap,
|
||||
SparseArray<List<Integer>> shortSliceIdToIndexToDeleteMap) {
|
||||
for (int i = 0; i <= 99; i++) {
|
||||
shortSliceIdToIndexMap.put(i, new ArrayList<>());
|
||||
shortSliceIdToIndexToDeleteMap.put(i, new ArrayList<>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < numbers.length; i++) {
|
||||
String numberString = String.valueOf(numbers[i]);
|
||||
if (numberString.length() > 1) {
|
||||
int sliceId = Integer.parseInt(numberString.substring(0, 2));
|
||||
shortSliceIdToIndexMap.get(sliceId).add(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numbersToDelete.length; i++) {
|
||||
String numberString = String.valueOf(numbersToDelete[i]);
|
||||
if (numberString.length() > 1) {
|
||||
int sliceId = Integer.parseInt(numberString.substring(0, 2));
|
||||
shortSliceIdToIndexToDeleteMap.get(sliceId).add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean partialClone(CommunityDatabaseDataSlice source, int shortSliceId,
|
||||
SparseArray<List<Integer>> shortSliceIdToIndexMap,
|
||||
SparseArray<List<Integer>> shortSliceIdToIndexToDeleteMap) {
|
||||
List<Integer> numberIndexList = shortSliceIdToIndexMap.get(shortSliceId);
|
||||
this.numberOfItems = numberIndexList.size();
|
||||
this.dbVersion = source.dbVersion;
|
||||
this.numbers = new long[this.numberOfItems];
|
||||
this.positiveRatingsCounts = new byte[this.numberOfItems];
|
||||
this.negativeRatingsCounts = new byte[this.numberOfItems];
|
||||
this.neutralRatingsCounts = new byte[this.numberOfItems];
|
||||
this.unknownData = new byte[this.numberOfItems];
|
||||
this.categories = new byte[this.numberOfItems];
|
||||
for (int i = 0; i < this.numberOfItems; i++) {
|
||||
int index = numberIndexList.get(i);
|
||||
this.numbers[i] = source.numbers[index];
|
||||
this.positiveRatingsCounts[i] = source.positiveRatingsCounts[index];
|
||||
this.negativeRatingsCounts[i] = source.negativeRatingsCounts[index];
|
||||
this.neutralRatingsCounts[i] = source.neutralRatingsCounts[index];
|
||||
this.unknownData[i] = source.unknownData[index];
|
||||
this.categories[i] = source.categories[index];
|
||||
}
|
||||
|
||||
List<Integer> numbersToDeleteIndexList = shortSliceIdToIndexToDeleteMap.get(shortSliceId);
|
||||
this.numbersToDelete = new long[numbersToDeleteIndexList.size()];
|
||||
for (int i = 0; i < numbersToDelete.length; i++) {
|
||||
this.numbersToDelete[i] = source.numbersToDelete[numbersToDeleteIndexList.get(i)];
|
||||
}
|
||||
|
||||
return this.numberOfItems > 0 || numbersToDelete.length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommunityDatabaseItem getDbItemByNumberInternal(long number, int index) {
|
||||
CommunityDatabaseItem communityDatabaseItem = new CommunityDatabaseItem();
|
||||
communityDatabaseItem.setNumber(numbers[index]);
|
||||
communityDatabaseItem.setPositiveRatingsCount(positiveRatingsCounts[index] & 255);
|
||||
communityDatabaseItem.setNegativeRatingsCount(negativeRatingsCounts[index] & 255);
|
||||
communityDatabaseItem.setNeutralRatingsCount(neutralRatingsCounts[index] & 255);
|
||||
communityDatabaseItem.setUnknownData(unknownData[index] & 255);
|
||||
communityDatabaseItem.setCategory(categories[index] & 255);
|
||||
return communityDatabaseItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamCheckHeader(String header) {
|
||||
if (!"YABF".equalsIgnoreCase(header) && !"MTZF".equalsIgnoreCase(header)
|
||||
&& !"MTZD".equalsIgnoreCase(header)) {
|
||||
throw new IllegalStateException("Invalid header. Actual value: " + header);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamReadPostHeaderData(LittleEndianDataInputStream stream) throws IOException {
|
||||
byte b = stream.readByte(); // ignored
|
||||
LOG.trace("loadFromStreamReadPostHeaderData() b={}", b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamReadPostVersionData(LittleEndianDataInputStream stream) throws IOException {
|
||||
String s = stream.readUtf8StringChars(2); // ignored
|
||||
int i = stream.readInt(); // ignored
|
||||
LOG.trace("loadFromStreamReadPostVersionData() s={}, i={}", s, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamInitFields() {
|
||||
positiveRatingsCounts = new byte[numberOfItems];
|
||||
negativeRatingsCounts = new byte[numberOfItems];
|
||||
neutralRatingsCounts = new byte[numberOfItems];
|
||||
unknownData = new byte[numberOfItems];
|
||||
categories = new byte[numberOfItems];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamLoadFields(int index, LittleEndianDataInputStream stream) throws IOException {
|
||||
positiveRatingsCounts[index] = stream.readByte();
|
||||
negativeRatingsCounts[index] = stream.readByte();
|
||||
neutralRatingsCounts[index] = stream.readByte();
|
||||
unknownData[index] = stream.readByte();
|
||||
categories[index] = stream.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamLoadExtras(LittleEndianDataInputStream stream) throws IOException {
|
||||
int numberOfItemsToDelete = stream.readInt();
|
||||
LOG.trace("loadFromStreamLoadExtras() numberOfItemsToDelete={}", numberOfItemsToDelete);
|
||||
|
||||
numbersToDelete = new long[numberOfItemsToDelete];
|
||||
for (int i = 0; i < numberOfItemsToDelete; i++) {
|
||||
numbersToDelete[i] = stream.readLong();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean writeMerged(CommunityDatabaseDataSlice newSlice, BufferedOutputStream outputStream) {
|
||||
LOG.debug("writeMerged() started with newSlice={}", newSlice);
|
||||
|
||||
try {
|
||||
int realNumberOfItems = 0;
|
||||
int newNumberOfItems = this.numberOfItems;
|
||||
if (newSlice != null) {
|
||||
if (this.numberOfItems > 0) {
|
||||
for (long n : newSlice.numbers) {
|
||||
if (indexOf(n) < 0) {
|
||||
newNumberOfItems++;
|
||||
}
|
||||
}
|
||||
for (long n : newSlice.numbersToDelete) {
|
||||
if (indexOf(n) < 0) {
|
||||
newNumberOfItems++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newNumberOfItems = newSlice.numberOfItems + newSlice.numbersToDelete.length;
|
||||
}
|
||||
}
|
||||
|
||||
LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(outputStream);
|
||||
stream.writeUtf8StringChars("YABF");
|
||||
stream.writeByte((byte) 1);
|
||||
stream.writeInt(newSlice != null ? newSlice.dbVersion : this.dbVersion);
|
||||
stream.writeUtf8StringChars("ww");
|
||||
stream.writeInt(0);
|
||||
stream.writeInt(newNumberOfItems);
|
||||
|
||||
if (this.numberOfItems >= 0) {
|
||||
int sourceIndex = 0;
|
||||
int newIndex = 0;
|
||||
int newDeletedIndex = 0;
|
||||
do {
|
||||
long sourceNumber = sourceIndex < this.numberOfItems ? this.numbers[sourceIndex] : Long.MAX_VALUE;
|
||||
boolean replacesSourceNumber = false;
|
||||
if (newSlice != null) {
|
||||
while (true) {
|
||||
long newNumber = (newIndex >= newSlice.numbers.length || newSlice.numbers[newIndex] > sourceNumber) ? 0 : newSlice.numbers[newIndex];
|
||||
long newDeletedNumber = (newDeletedIndex >= newSlice.numbersToDelete.length || newSlice.numbersToDelete[newDeletedIndex] > sourceNumber) ? 0 : newSlice.numbersToDelete[newDeletedIndex];
|
||||
if (newNumber <= 0 || (newDeletedNumber != 0 && newNumber >= newDeletedNumber)) {
|
||||
if (newDeletedNumber <= 0) {
|
||||
break;
|
||||
}
|
||||
stream.writeLong(newSlice.numbersToDelete[newDeletedIndex]);
|
||||
byte zero = (byte) 0;
|
||||
stream.writeByte(zero);
|
||||
stream.writeByte(zero);
|
||||
stream.writeByte(zero);
|
||||
stream.writeByte(zero);
|
||||
stream.writeByte(zero);
|
||||
replacesSourceNumber = newSlice.numbersToDelete[newDeletedIndex] == sourceNumber;
|
||||
newDeletedIndex++;
|
||||
realNumberOfItems++;
|
||||
} else {
|
||||
stream.writeLong(newSlice.numbers[newIndex]);
|
||||
stream.writeByte(newSlice.positiveRatingsCounts[newIndex]);
|
||||
stream.writeByte(newSlice.negativeRatingsCounts[newIndex]);
|
||||
stream.writeByte(newSlice.neutralRatingsCounts[newIndex]);
|
||||
stream.writeByte(newSlice.unknownData[newIndex]);
|
||||
stream.writeByte(newSlice.categories[newIndex]);
|
||||
replacesSourceNumber = newSlice.numbers[newIndex] == sourceNumber;
|
||||
newIndex++;
|
||||
realNumberOfItems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!replacesSourceNumber && sourceIndex < this.numberOfItems) {
|
||||
stream.writeLong(this.numbers[sourceIndex]);
|
||||
stream.writeByte(this.positiveRatingsCounts[sourceIndex]);
|
||||
stream.writeByte(this.negativeRatingsCounts[sourceIndex]);
|
||||
stream.writeByte(this.neutralRatingsCounts[sourceIndex]);
|
||||
stream.writeByte(this.unknownData[sourceIndex]);
|
||||
stream.writeByte(this.categories[sourceIndex]);
|
||||
realNumberOfItems++;
|
||||
}
|
||||
} while (sourceIndex++ != this.numberOfItems);
|
||||
}
|
||||
if (realNumberOfItems != newNumberOfItems) {
|
||||
LOG.error("writeMerged() realNumberOfItems={}, newNumberOfItems={}, dbVersion={}, newSlice.dbVersion={}",
|
||||
realNumberOfItems, newNumberOfItems, dbVersion, newSlice != null ? newSlice.dbVersion : 0);
|
||||
throw new IllegalStateException("writeMerged results in invalid realNumberOfItems!");
|
||||
}
|
||||
stream.writeUtf8StringChars("CP");
|
||||
stream.writeInt(0);
|
||||
stream.writeUtf8StringChars("YABEND");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LOG.error("writeMerged()", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.model.NumberCategory;
|
||||
|
||||
public class CommunityDatabaseItem {
|
||||
|
||||
private long number;
|
||||
|
||||
private int positiveRatingsCount;
|
||||
private int negativeRatingsCount;
|
||||
private int neutralRatingsCount;
|
||||
|
||||
private int unknownData;
|
||||
|
||||
private int category;
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(long j) {
|
||||
this.number = j;
|
||||
}
|
||||
|
||||
public int getPositiveRatingsCount() {
|
||||
return this.positiveRatingsCount;
|
||||
}
|
||||
|
||||
public void setPositiveRatingsCount(int c) {
|
||||
this.positiveRatingsCount = c;
|
||||
}
|
||||
|
||||
public int getNegativeRatingsCount() {
|
||||
return this.negativeRatingsCount;
|
||||
}
|
||||
|
||||
public void setNegativeRatingsCount(int c) {
|
||||
this.negativeRatingsCount = c;
|
||||
}
|
||||
|
||||
public int getNeutralRatingsCount() {
|
||||
return this.neutralRatingsCount;
|
||||
}
|
||||
|
||||
public void setNeutralRatingsCount(int c) {
|
||||
this.neutralRatingsCount = c;
|
||||
}
|
||||
|
||||
public int getUnknownData() {
|
||||
return unknownData;
|
||||
}
|
||||
|
||||
public void setUnknownData(int v) {
|
||||
this.unknownData = v;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return this.category;
|
||||
}
|
||||
|
||||
public void setCategory(int c) {
|
||||
this.category = c;
|
||||
}
|
||||
|
||||
public boolean hasRatings() {
|
||||
return positiveRatingsCount + negativeRatingsCount + negativeRatingsCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommunityDatabaseItem{" +
|
||||
"number=" + number +
|
||||
", positiveRatingsCount=" + positiveRatingsCount +
|
||||
", negativeRatingsCount=" + negativeRatingsCount +
|
||||
", neutralRatingsCount=" + neutralRatingsCount +
|
||||
", unknownData=" + unknownData +
|
||||
", category=" + NumberCategory.getById(category) + " (" + category + ")" +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
class DatabaseDataSliceNode {
|
||||
|
||||
private DatabaseDataSliceNode[] sliceTree;
|
||||
|
||||
public int init(String data, int offset) {
|
||||
this.sliceTree = new DatabaseDataSliceNode[10];
|
||||
|
||||
for (int i = 0; i <= 9; i++) {
|
||||
if (data.charAt(offset) == '*' || data.charAt(offset) == '+') {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DatabaseDataSliceNode node = new DatabaseDataSliceNode();
|
||||
offset = node.init(data, offset + 1);
|
||||
sliceTree[i] = node;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getSliceId(int currentNumber, String number) {
|
||||
int currentDigit = Integer.parseInt(number.substring(0, 1));
|
||||
|
||||
number = number.substring(1);
|
||||
|
||||
currentNumber = currentNumber * 10 + currentDigit;
|
||||
|
||||
if (sliceTree[currentDigit] == null) {
|
||||
return currentNumber;
|
||||
}
|
||||
|
||||
if (number.length() > 0) {
|
||||
DatabaseDataSliceNode sliceNode = sliceTree[currentDigit];
|
||||
return sliceNode.getSliceId(currentNumber, number);
|
||||
}
|
||||
|
||||
// the number is shorter
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.Storage;
|
||||
import dummydomain.yetanothercallblocker.sia.network.DbDownloader;
|
||||
import dummydomain.yetanothercallblocker.sia.utils.FileUtils;
|
||||
|
||||
public class DbManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DbManager.class);
|
||||
|
||||
private static final String DEFAULT_URL = "https://gitlab.com/xynngh/YetAnotherCallBlocker_data/raw/zip_v1/archives/sia.zip";
|
||||
|
||||
private final Storage storage;
|
||||
private final String pathPrefix;
|
||||
|
||||
public DbManager(Storage storage, String pathPrefix) {
|
||||
this.storage = storage;
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
public boolean downloadMainDb() {
|
||||
return downloadMainDb(DEFAULT_URL);
|
||||
}
|
||||
|
||||
public boolean downloadMainDb(String url) {
|
||||
LOG.debug("downloadMainDb() started");
|
||||
|
||||
File dataDir = new File(storage.getDataDirPath());
|
||||
|
||||
String siaDir = pathPrefix;
|
||||
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, storage.getDataDirPath() + tmpUpdateDir)) {
|
||||
LOG.debug("downloadMainDb() downloaded and unpacked");
|
||||
|
||||
File old = new File(dataDir, siaDir);
|
||||
if (old.exists() && !old.renameTo(new File(dataDir, oldDir))) {
|
||||
LOG.warn("downloadMainDb() couldn't rename sia to old");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new File(dataDir, tmpUpdateDir).renameTo(new File(dataDir, siaDir))) {
|
||||
LOG.warn("downloadMainDb() couldn't rename tmp to sia");
|
||||
return false;
|
||||
}
|
||||
|
||||
FileUtils.delete(dataDir, oldDir);
|
||||
|
||||
LOG.debug("downloadMainDb() folders moved");
|
||||
return true;
|
||||
} else {
|
||||
LOG.warn("downloadMainDb() failed downloading");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.Storage;
|
||||
|
||||
public class FeaturedDatabase extends AbstractDatabase<FeaturedDatabaseDataSlice, FeaturedDatabaseItem> {
|
||||
|
||||
public FeaturedDatabase(Storage storage, Source source, String pathPrefix) {
|
||||
super(storage, source, pathPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNamePrefix() {
|
||||
return "featured_slice_";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FeaturedDatabaseItem getDbItemByNumberInternal(long number) {
|
||||
FeaturedDatabaseDataSlice slice = getDataSlice(number);
|
||||
return slice != null ? slice.getDbItemByNumber(number) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FeaturedDatabaseDataSlice createDbDataSlice() {
|
||||
return new FeaturedDatabaseDataSlice();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.utils.LittleEndianDataInputStream;
|
||||
|
||||
public class FeaturedDatabaseDataSlice extends AbstractDatabaseDataSlice<FeaturedDatabaseItem> {
|
||||
|
||||
private String[] names;
|
||||
|
||||
@Override
|
||||
protected FeaturedDatabaseItem getDbItemByNumberInternal(long number, int index) {
|
||||
return new FeaturedDatabaseItem(number, names[index]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamCheckHeader(String header) {
|
||||
if (!"YABX".equalsIgnoreCase(header) && !"MTZX".equalsIgnoreCase(header)) {
|
||||
throw new IllegalStateException("Invalid header. Actual value: " + header);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamInitFields() {
|
||||
names = new String[numberOfItems];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFromStreamLoadFields(int index, LittleEndianDataInputStream stream) throws IOException {
|
||||
int nameLength = stream.readInt();
|
||||
names[index] = stream.readUtf8StringChars(nameLength);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.model.database;
|
||||
|
||||
public class FeaturedDatabaseItem {
|
||||
|
||||
private long number;
|
||||
|
||||
private String name;
|
||||
|
||||
public FeaturedDatabaseItem(long number, String name) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(long number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FeaturedDatabaseItem{" +
|
||||
"number=" + number +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.network;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import dummydomain.yetanothercallblocker.sia.utils.Utils;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class WebService {
|
||||
|
||||
public interface WSParameterProvider {
|
||||
String getAppId();
|
||||
|
||||
String getDevice();
|
||||
String getModel();
|
||||
String getManufacturer();
|
||||
int getAndroidApi();
|
||||
|
||||
String getAppFamily();
|
||||
int getAppVersion();
|
||||
int getDbVersion();
|
||||
String getCountry();
|
||||
}
|
||||
|
||||
public static abstract class DefaultWSParameterProvider implements WSParameterProvider {
|
||||
@Override
|
||||
public String getDevice() {
|
||||
return "dreamlte";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel() {
|
||||
return "SM-G950F";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Samsung";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAndroidApi() {
|
||||
return 26;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppFamily() {
|
||||
return "SIA-NEXT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCountry() {
|
||||
return "US";
|
||||
}
|
||||
}
|
||||
|
||||
public static class WSResponse {
|
||||
private boolean successful;
|
||||
private int responseCode;
|
||||
private String responseMessage;
|
||||
private JSONObject jsonObject;
|
||||
|
||||
public void setSuccessful(boolean successful) {
|
||||
this.successful = successful;
|
||||
}
|
||||
|
||||
public boolean getSuccessful() {
|
||||
return this.successful;
|
||||
}
|
||||
|
||||
public void setResponseCode(int i) {
|
||||
this.responseCode = i;
|
||||
}
|
||||
|
||||
public void setResponseMessage(String str) {
|
||||
this.responseMessage = str;
|
||||
}
|
||||
|
||||
public String getResponseMessage() {
|
||||
return this.responseMessage;
|
||||
}
|
||||
|
||||
public void setJsonObject(JSONObject jSONObject) {
|
||||
this.jsonObject = jSONObject;
|
||||
}
|
||||
|
||||
public final JSONObject getJsonObject() {
|
||||
return this.jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebService.class);
|
||||
|
||||
private static final String GET_DATABASE_URL_PART = "/get-database";
|
||||
private static final String GET_REVIEWS_URL_PART = "/get-reviews";
|
||||
|
||||
private final WSParameterProvider parameterProvider;
|
||||
|
||||
public WebService(WSParameterProvider parameterProvider) {
|
||||
this.parameterProvider = parameterProvider;
|
||||
}
|
||||
|
||||
public String getGetDatabaseUrlPart() {
|
||||
return GET_DATABASE_URL_PART;
|
||||
}
|
||||
|
||||
public String getGetReviewsUrlPart() {
|
||||
return GET_REVIEWS_URL_PART;
|
||||
}
|
||||
|
||||
public Response call(String path, Map<String, String> params) {
|
||||
LOG.debug("call() started; path={}", path);
|
||||
|
||||
// TODO: retries
|
||||
|
||||
Request request = new Request.Builder().url(addHostname(path)).post(createRequestBody(params)).build();
|
||||
try {
|
||||
return new OkHttpClient().newCall(request).execute();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("call()", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public WSResponse callForJson(String path, Map<String, String> params) {
|
||||
LOG.debug("callForJson() started; path={}", path);
|
||||
|
||||
try {
|
||||
Response response = call(path, params);
|
||||
if (response != null) {
|
||||
WSResponse wsResponse = new WSResponse();
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
String bodyString = body.string();
|
||||
LOG.trace("callForJson() response body: {}", bodyString);
|
||||
try {
|
||||
wsResponse.setJsonObject(new JSONObject(bodyString));
|
||||
JSONObject c = wsResponse.getJsonObject();
|
||||
wsResponse.setSuccessful(c.getBoolean("success"));
|
||||
} catch (Exception e) {
|
||||
LOG.error("callForJson()", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.trace("callForJson() response is not successful");
|
||||
wsResponse.setResponseCode(response.code());
|
||||
wsResponse.setResponseMessage(response.message());
|
||||
response.close();
|
||||
}
|
||||
return wsResponse;
|
||||
} else {
|
||||
LOG.warn("callForJson() response is null");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("callForJson()", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String addHostname(String path) {
|
||||
return "https://aapi.shouldianswer.net/srvapp" + path;
|
||||
}
|
||||
|
||||
private RequestBody createRequestBody(Map<String, String> params) {
|
||||
if (params == null) params = new HashMap<>();
|
||||
|
||||
params.put("_appId", parameterProvider.getAppId());
|
||||
|
||||
params.put("_device", parameterProvider.getDevice());
|
||||
params.put("_model", parameterProvider.getModel());
|
||||
params.put("_manufacturer", parameterProvider.getManufacturer());
|
||||
params.put("_api", String.valueOf(parameterProvider.getAndroidApi()));
|
||||
|
||||
params.put("_appFamily", parameterProvider.getAppFamily());
|
||||
params.put("_appVer", String.valueOf(parameterProvider.getAppVersion()));
|
||||
params.put("_dbVer", String.valueOf(parameterProvider.getDbVersion()));
|
||||
params.put("_country", parameterProvider.getCountry());
|
||||
|
||||
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
|
||||
|
||||
StringBuilder paramsSb = new StringBuilder();
|
||||
for (String key : params.keySet()) {
|
||||
String val = params.get(key);
|
||||
if (val != null) {
|
||||
paramsSb.append(val);
|
||||
bodyBuilder.addFormDataPart(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
bodyBuilder.addFormDataPart("_checksum", Utils.md5String(paramsSb.toString() + "saltandmira2"));
|
||||
|
||||
return bodyBuilder.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
|
||||
|
||||
public static File createDirectory(String path) {
|
||||
return createDirectory(new File(path));
|
||||
}
|
||||
|
||||
public static File createDirectory(String base, String dir) {
|
||||
return createDirectory(new File(base, dir));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.utils;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class LittleEndianDataInputStream {
|
||||
|
||||
private InputStream in;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
public LittleEndianDataInputStream(InputStream in) {
|
||||
this(in, 200);
|
||||
}
|
||||
|
||||
public LittleEndianDataInputStream(InputStream in, int bufferSize) {
|
||||
this.in = in;
|
||||
buffer = new byte[bufferSize];
|
||||
}
|
||||
|
||||
public byte readByte() throws IOException {
|
||||
int ch = in.read();
|
||||
if (ch < 0) throw new EOFException();
|
||||
return (byte)(ch);
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
if (in.read(buffer, 0, 4) == 4) {
|
||||
return (((buffer[0] & 0xff) | ((buffer[1] & 0xff) << 8))
|
||||
| ((buffer[2] & 0xff) << 16)) | ((buffer[3] & 0xff) << 24);
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
public long readLong() throws IOException {
|
||||
if (in.read(buffer, 0, 8) == 8) {
|
||||
return (((((((((((((((long) (buffer[7] & 0xff)) << 8)
|
||||
| ((long) (buffer[6] & 0xff))) << 8)
|
||||
| ((long) (buffer[5] & 0xff))) << 8)
|
||||
| ((long) (buffer[4] & 0xff))) << 8)
|
||||
| ((long) (buffer[3] & 0xff))) << 8)
|
||||
| ((long) (buffer[2] & 0xff))) << 8)
|
||||
| ((long) (buffer[1] & 0xff))) << 8)
|
||||
| ((long) (buffer[0] & 0xff));
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
public String readUtf8StringChars(int length) throws IOException {
|
||||
if (buffer.length < length) buffer = new byte[length];
|
||||
|
||||
if (in.read(buffer, 0, length) == length) {
|
||||
return new String(buffer, 0, length, Charset.forName("UTF-8"));
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class LittleEndianDataOutputStream {
|
||||
|
||||
private OutputStream out;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
public LittleEndianDataOutputStream(OutputStream out) {
|
||||
this(out, 8);
|
||||
}
|
||||
|
||||
public LittleEndianDataOutputStream(OutputStream out, int bufferSize) {
|
||||
this.out = out;
|
||||
buffer = new byte[bufferSize];
|
||||
}
|
||||
|
||||
public void writeByte(byte b) throws IOException {
|
||||
out.write(b);
|
||||
}
|
||||
|
||||
public void writeInt(int i) throws IOException {
|
||||
buffer[0] = (byte) (i & 0xff);
|
||||
buffer[1] = (byte) ((i >>> 8) & 0xff);
|
||||
buffer[2] = (byte) ((i >>> 16) & 0xff);
|
||||
buffer[3] = (byte) ((i >>> 24) & 0xff);
|
||||
out.write(buffer, 0, 4);
|
||||
}
|
||||
|
||||
public void writeLong(long l) throws IOException {
|
||||
buffer[0] = (byte) (l & 0xff);
|
||||
buffer[1] = (byte) ((l >>> 8) & 0xff);
|
||||
buffer[2] = (byte) ((l >>> 16) & 0xff);
|
||||
buffer[3] = (byte) ((l >>> 24) & 0xff);
|
||||
buffer[4] = (byte) ((l >>> 32) & 0xff);
|
||||
buffer[5] = (byte) ((l >>> 40) & 0xff);
|
||||
buffer[6] = (byte) ((l >>> 48) & 0xff);
|
||||
buffer[7] = (byte) ((l >>> 56) & 0xff);
|
||||
out.write(buffer, 0, 8);
|
||||
}
|
||||
|
||||
public void writeUtf8StringChars(String str) throws IOException {
|
||||
byte[] bytes = str.getBytes(Charset.forName("UTF-8"));
|
||||
out.write(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package dummydomain.yetanothercallblocker.sia.utils;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static String md5String(String str) {
|
||||
return new String(Hex.encodeHex(DigestUtils.md5(str.getBytes())));
|
||||
}
|
||||
|
||||
public static String generateAppId() {
|
||||
UUID randomUUID = UUID.randomUUID();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(16);
|
||||
buffer.putLong(randomUUID.getMostSignificantBits());
|
||||
buffer.putLong(randomUUID.getLeastSignificantBits());
|
||||
|
||||
return Base64.encodeToString(buffer.array(), Base64.NO_PADDING)
|
||||
.replace('/', '_')
|
||||
.replace('\\', '-')
|
||||
.replaceAll("\n", "");
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ allprojects {
|
|||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue