Database downloader changes

- Store the new database separately and move into place
- Check for network connection before trying to download
- Don't immediately delete the database on download start
- Only delete new database on download failure
- Wait until all downloads complete before attempting reload

Signed-off-by: Tad <tad@spotco.us>
This commit is contained in:
Tad 2023-12-22 15:30:53 -05:00
parent 55305f88f1
commit fa25395a08
No known key found for this signature in database
GPG key ID: B286E9F57A07424B
6 changed files with 70 additions and 11 deletions

View file

@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner" applicationId "us.spotco.malwarescanner"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 32 targetSdkVersion 32
versionCode 112 versionCode 113
versionName "2.35" versionName "2.36"
resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN' resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN'
} }
buildTypes { buildTypes {

View file

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="us.spotco.malwarescanner"> package="us.spotco.malwarescanner">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

View file

@ -53,6 +53,8 @@ class Database {
private static final DateFormat dateFormat = DateFormat.getDateInstance(); private static final DateFormat dateFormat = DateFormat.getDateInstance();
private static final ConcurrentLinkedQueue<AsyncTask<?, ?, ?>> downloadFutures = new ConcurrentLinkedQueue<>();
public Database(TextView log) { public Database(TextView log) {
Database.log = log; Database.log = log;
} }
@ -62,7 +64,15 @@ class Database {
} }
public static boolean isDatabaseLoaded() { public static boolean isDatabaseLoaded() {
return areDatabasesAvailable() && databaseFullyLoaded; return areDatabasesAvailable() && !isDatabaseLoading();
}
public static boolean isDatabaseLoading() {
return !databaseFullyLoaded && databaseCurrentlyLoading;
}
public static boolean hasDownloadsRunning() {
return downloadFutures.size() > 0;
} }
public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) { public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
@ -106,7 +116,7 @@ class Database {
try { try {
boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey); boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey);
if (validated) { if (validated) {
Log.d("Hypatia", "Successfully validated database"); Log.d("Hypatia", "Successfully validated database: " + databaseLocation.getName());
FileInputStream databaseLoading = new FileInputStream(databaseLocation); FileInputStream databaseLoading = new FileInputStream(databaseLocation);
switch (databaseLocation.getName()) { switch (databaseLocation.getName()) {
case "hypatia-md5-bloom.bin": case "hypatia-md5-bloom.bin":
@ -139,13 +149,29 @@ class Database {
} }
public static class Downloader extends AsyncTask<Object, String, String> { public static class Downloader extends AsyncTask<Object, String, String> {
@Override
protected void onPreExecute() {
Database.downloadFutures.add(this);
super.onPreExecute();
}
@Override
protected void onPostExecute(String s) {
Database.downloadFutures.remove(this);
super.onPostExecute(s);
}
@Override @Override
protected String doInBackground(Object... objects) { protected String doInBackground(Object... objects) {
boolean onionRouting = (boolean) objects[0]; boolean onionRouting = (boolean) objects[0];
String url = (String) objects[1]; String url = (String) objects[1];
File out = new File((String) objects[2]); File out = new File((String) objects[2]);
File outNew = new File((String) objects[2] + ".new");
String baseURL = (String) objects[3]; String baseURL = (String) objects[3];
try { try {
if(outNew.exists()) {
outNew.delete();
}
HttpURLConnection connection; HttpURLConnection connection;
if (onionRouting) { if (onionRouting) {
Utils.waitUntilOrbotIsAvailable(); Utils.waitUntilOrbotIsAvailable();
@ -167,10 +193,7 @@ class Database {
int res = connection.getResponseCode(); int res = connection.getResponseCode();
if (res != 304) { if (res != 304) {
if (res == 200) { if (res == 200) {
if (out.exists()) { FileOutputStream fileOutputStream = new FileOutputStream(outNew);
out.delete();
}
FileOutputStream fileOutputStream = new FileOutputStream(out);
final byte[] data = new byte[1024]; final byte[] data = new byte[1024];
int count; int count;
@ -179,6 +202,8 @@ class Database {
} }
fileOutputStream.close(); fileOutputStream.close();
outNew.renameTo(out); //Move the new file into place
publishProgress(url.replaceAll(baseURL, "") publishProgress(url.replaceAll(baseURL, "")
+ "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success)
+ "\n\t" + Utils.getContext().getString(R.string.main_database_released_on, lastModifiedServer) + "\n"); + "\n\t" + Utils.getContext().getString(R.string.main_database_released_on, lastModifiedServer) + "\n");
@ -193,7 +218,9 @@ class Database {
connection.disconnect(); connection.disconnect();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
out.delete(); if(outNew.exists()) {
outNew.delete();
}
publishProgress(url.replaceAll(baseURL, "") publishProgress(url.replaceAll(baseURL, "")
+ "\n" + Utils.getContext().getString(R.string.main_database_download_error_logcat) + "\n"); + "\n" + Utils.getContext().getString(R.string.main_database_download_error_logcat) + "\n");
} }

View file

@ -35,6 +35,7 @@ import android.os.Environment;
import android.provider.Settings; import android.provider.Settings;
import android.text.InputType; import android.text.InputType;
import android.text.method.ScrollingMovementMethod; import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.WindowManager; import android.view.WindowManager;
@ -152,6 +153,8 @@ public class MainActivity extends Activity {
case R.id.mnuUpdateDatabase: case R.id.mnuUpdateDatabase:
if (malwareScanner.running) { if (malwareScanner.running) {
logView.append(getString(R.string.lblScanRunning) + "\n"); logView.append(getString(R.string.lblScanRunning) + "\n");
} else if (!Utils.isNetworkAvailable(this)) {
logView.append(getString(R.string.lblNoNetwork) + "\n");
} else { } else {
if (prefs.getBoolean("ONION_ROUTING", false)) { if (prefs.getBoolean("ONION_ROUTING", false)) {
Utils.requestStartOrbot(this); Utils.requestStartOrbot(this);
@ -276,9 +279,27 @@ public class MainActivity extends Activity {
private void updateDatabase() { private void updateDatabase() {
new Database(findViewById(R.id.txtLogOutput)); new Database(findViewById(R.id.txtLogOutput));
Database.updateDatabase(this, Database.signatureDatabases); if(!Database.isDatabaseLoading()) {
Database.updateDatabase(this, Database.signatureDatabases);
} else {
Log.w("Hypatia", "Database is loading, not downloading!");
}
if (Database.isDatabaseLoaded()) { if (Database.isDatabaseLoaded()) {
Utils.getThreadPoolExecutor().execute(() -> Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases)); Utils.getThreadPoolExecutor().execute(() -> {
try {
Thread.sleep(500);
Log.w("Hypatia", "Invoking database reload!");
while (Database.hasDownloadsRunning()) {
Thread.sleep(500);
Log.w("Hypatia", "Download in progress, waiting!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.w("Hypatia", "Really reloading database!");
Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases);
});
} }
} }

View file

@ -22,6 +22,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build; import android.os.Build;
import java.io.File; import java.io.File;
@ -169,6 +171,13 @@ class Utils {
} }
} }
//Credit: https://stackoverflow.com/a/4239019
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager != null ? connectivityManager.getActiveNetworkInfo() : null;
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
public static Context getContext() { public static Context getContext() {
return context; return context;
} }

View file

@ -62,4 +62,5 @@
<string name="scan_control">Scan Control</string> <string name="scan_control">Scan Control</string>
<string name="lblScanRunning">Skipping action, a scan is running!</string> <string name="lblScanRunning">Skipping action, a scan is running!</string>
<string name="lblSigningKey">Database signing key</string> <string name="lblSigningKey">Database signing key</string>
<string name="lblNoNetwork">No network connected!</string>
</resources> </resources>