mirror of
https://gitlab.com/xynngh/YetAnotherCallBlocker.git
synced 2025-02-16 20:00:35 +01:00
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'
|
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||||
//noinspection GradleDependency: 3.12.* is the latest version compatible with Android <5
|
//noinspection GradleDependency: 3.12.* is the latest version compatible with Android <5
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
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.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview: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 {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user