Config db to use cascade delete (#1408)

Co-authored-by: Howard Wu <40033067+Howard20181@users.noreply.github.com>
This commit is contained in:
LoveSy 2021-11-16 18:53:29 +08:00 committed by GitHub
parent 748e67e72b
commit 5656f1b330
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 105 additions and 66 deletions

View File

@ -73,11 +73,13 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
public class ConfigManager { public class ConfigManager {
private static ConfigManager instance = null; private static ConfigManager instance = null;
private static final int DB_VERSION = 2;
private final SQLiteDatabase db = private final SQLiteDatabase db =
SQLiteDatabase.openOrCreateDatabase(ConfigFileManager.dbPath, null); SQLiteDatabase.openOrCreateDatabase(ConfigFileManager.dbPath, null);
@ -134,7 +136,11 @@ public class ConfigManager {
"mid integer," + "mid integer," +
"app_pkg_name text NOT NULL," + "app_pkg_name text NOT NULL," +
"user_id integer NOT NULL," + "user_id integer NOT NULL," +
"PRIMARY KEY (mid, app_pkg_name, user_id)" + "PRIMARY KEY (mid, app_pkg_name, user_id)," +
"CONSTRAINT scope_module_constraint" +
" FOREIGN KEY (mid)" +
" REFERENCES modules (mid)" +
" ON DELETE CASCADE" +
");"); ");");
private final SQLiteStatement createConfigTable = db.compileStatement("CREATE TABLE IF NOT EXISTS configs (" + private final SQLiteStatement createConfigTable = db.compileStatement("CREATE TABLE IF NOT EXISTS configs (" +
"module_pkg_name text NOT NULL," + "module_pkg_name text NOT NULL," +
@ -142,7 +148,11 @@ public class ConfigManager {
"`group` text NOT NULL," + "`group` text NOT NULL," +
"`key` text NOT NULL," + "`key` text NOT NULL," +
"data blob NOT NULL," + "data blob NOT NULL," +
"PRIMARY KEY (module_pkg_name, user_id, `group`, `key`)" + "PRIMARY KEY (module_pkg_name, user_id, `group`, `key`)," +
"CONSTRAINT config_module_constraint" +
" FOREIGN KEY (module_pkg_name)" +
" REFERENCES modules (module_pkg_name)" +
" ON DELETE CASCADE" +
");"); ");");
private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>(); private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
@ -276,18 +286,64 @@ public class ConfigManager {
cacheThread.start(); cacheThread.start();
cacheHandler = new Handler(cacheThread.getLooper()); cacheHandler = new Handler(cacheThread.getLooper());
createTables(); initDB();
updateConfig(); updateConfig();
// must ensure cache is valid for later usage // must ensure cache is valid for later usage
updateCaches(true); updateCaches(true);
} }
private void createTables() {
private <T> T executeInTransaction(Supplier<T> execution) {
try { try {
db.beginTransaction();
var res = execution.get();
db.setTransactionSuccessful();
return res;
} finally {
db.endTransaction();
}
}
private void executeInTransaction(Runnable execution) {
executeInTransaction((Supplier<Void>) () -> {
execution.run();
return null;
});
}
private void initDB() {
try {
db.setForeignKeyConstraintsEnabled(true);
switch (db.getVersion()) {
case 0:
executeInTransaction(() -> {
createModulesTable.execute(); createModulesTable.execute();
createScopeTable.execute(); createScopeTable.execute();
createConfigTable.execute(); createConfigTable.execute();
var values = new ContentValues();
values.put("module_pkg_name", "lspd");
values.put("apk_path", ConfigFileManager.managerApkPath.toString());
// dummy module for config
db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE);
});
case 1:
executeInTransaction(() -> {
db.compileStatement("DROP INDEX IF EXISTS configs_idx;").execute();
db.compileStatement("DROP TABLE IF EXISTS config;").execute();
db.compileStatement("ALTER TABLE scope RENAME TO old_scope;").execute();
db.compileStatement("ALTER TABLE configs RENAME TO old_configs;").execute();
createConfigTable.execute();
createScopeTable.execute();
db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute(); db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute();
db.compileStatement("INSERT INTO scope SELECT * FROM old_scope;").execute();
db.compileStatement("INSERT INTO configs SELECT * FROM old_configs;").execute();
db.compileStatement("DROP TABLE old_scope;").execute();
db.compileStatement("DROP TABLE old_configs;").execute();
});
default:
break;
}
db.setVersion(DB_VERSION);
} catch (Throwable e) { } catch (Throwable e) {
Log.e(TAG, "init db", e); Log.e(TAG, "init db", e);
} }
@ -381,6 +437,7 @@ public class ConfigManager {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
String packageName = cursor.getString(pkgNameIdx); String packageName = cursor.getString(pkgNameIdx);
String apkPath = cursor.getString(apkPathIdx); String apkPath = cursor.getString(apkPathIdx);
if (packageName.equals("lspd")) continue;
// if still present after removeIf, this package did not change. // if still present after removeIf, this package did not change.
var oldModule = cachedModule.get(packageName); var oldModule = cachedModule.get(packageName);
PackageInfo pkgInfo = null; PackageInfo pkgInfo = null;
@ -556,10 +613,9 @@ public class ConfigManager {
@Nullable @Nullable
public List<Application> getModuleScope(String packageName) { public List<Application> getModuleScope(String packageName) {
int mid = getModuleId(packageName); if (packageName.equals("lspd")) return null;
if (mid == -1) return null;
try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"app_pkg_name", "user_id"}, try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"app_pkg_name", "user_id"},
"scope.mid = ?", new String[]{String.valueOf(mid)}, null, null, null)) { "modules.module_pkg_name = ?", new String[]{packageName}, null, null, null)) {
if (cursor == null) { if (cursor == null) {
return null; return null;
} }
@ -598,7 +654,7 @@ public class ConfigManager {
} }
public boolean updateModuleApkPath(String packageName, String apkPath, boolean force) { public boolean updateModuleApkPath(String packageName, String apkPath, boolean force) {
if (apkPath == null) return false; if (apkPath == null || packageName.equals("lspd")) return false;
if (db.inTransaction()) { if (db.inTransaction()) {
Log.w(TAG, "update module apk path should not be called inside transaction"); Log.w(TAG, "update module apk path should not be called inside transaction");
return false; return false;
@ -628,6 +684,7 @@ public class ConfigManager {
// Only be called before updating modules. No need to cache. // Only be called before updating modules. No need to cache.
private int getModuleId(String packageName) { private int getModuleId(String packageName) {
if (packageName.equals("lspd")) return -1;
if (db.inTransaction()) { if (db.inTransaction()) {
Log.w(TAG, "get module id should not be called inside transaction"); Log.w(TAG, "get module id should not be called inside transaction");
return -1; return -1;
@ -648,8 +705,7 @@ public class ConfigManager {
self.packageName = packageName; self.packageName = packageName;
self.userId = 0; self.userId = 0;
scopes.add(self); scopes.add(self);
try { executeInTransaction(() -> {
db.beginTransaction();
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)}); db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
for (Application app : scopes) { for (Application app : scopes) {
if (app.packageName.equals("android") && app.userId != 0) continue; if (app.packageName.equals("android") && app.userId != 0) continue;
@ -659,10 +715,7 @@ public class ConfigManager {
values.put("user_id", app.userId); values.put("user_id", app.userId);
db.insertWithOnConflict("scope", null, values, SQLiteDatabase.CONFLICT_IGNORE); db.insertWithOnConflict("scope", null, values, SQLiteDatabase.CONFLICT_IGNORE);
} }
} finally { });
db.setTransactionSuccessful();
db.endTransaction();
}
// Called by manager, should be async // Called by manager, should be async
updateCaches(false); updateCaches(false);
return true; return true;
@ -677,7 +730,9 @@ public class ConfigManager {
int modulePkgNameIdx = cursor.getColumnIndex("module_pkg_name"); int modulePkgNameIdx = cursor.getColumnIndex("module_pkg_name");
HashSet<String> result = new HashSet<>(); HashSet<String> result = new HashSet<>();
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
result.add(cursor.getString(modulePkgNameIdx)); var pkgName = cursor.getString(modulePkgNameIdx);
if (pkgName.equals("lspd")) continue;
result.add(pkgName);
} }
return result.toArray(new String[0]); return result.toArray(new String[0]);
} }
@ -696,16 +751,8 @@ public class ConfigManager {
} }
private boolean removeModuleWithoutCache(String packageName) { private boolean removeModuleWithoutCache(String packageName) {
int mid = getModuleId(packageName); if (packageName.equals("lspd")) return false;
if (mid == -1) return false; boolean res = executeInTransaction(() -> db.delete("modules", "module_pkg_name = ?", new String[]{packageName}) > 0);
try {
db.beginTransaction();
db.delete("modules", "mid = ?", new String[]{String.valueOf(mid)});
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
try { try {
for (var user : UserService.getUsers()) { for (var user : UserService.getUsers()) {
removeModulePrefs(user.id, packageName); removeModulePrefs(user.id, packageName);
@ -713,64 +760,56 @@ public class ConfigManager {
} catch (Throwable e) { } catch (Throwable e) {
Log.w(TAG, "remove module prefs for " + packageName); Log.w(TAG, "remove module prefs for " + packageName);
} }
return true; return res;
} }
private void removeModuleScopeWithoutCache(Application module) { private boolean removeModuleScopeWithoutCache(Application module) {
int mid = getModuleId(module.packageName); if (module.packageName.equals("lspd")) return false;
if (mid == -1) return; boolean res = executeInTransaction(() -> db.delete("scope", "mid = ? and user_id = ?", new String[]{module.packageName, String.valueOf(module.userId)}) > 0);
try {
db.beginTransaction();
db.delete("scope", "mid = ? and user_id = ?", new String[]{String.valueOf(mid), String.valueOf(module.userId)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
try { try {
removeModulePrefs(module.userId, module.packageName); removeModulePrefs(module.userId, module.packageName);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "removeModulePrefs", e); Log.w(TAG, "removeModulePrefs", e);
} }
return res;
} }
private void removeAppWithoutCache(Application app) { private boolean removeAppWithoutCache(Application app) {
db.delete("scope", "app_pkg_name = ? AND user_id=?", return executeInTransaction(() -> db.delete("scope", "app_pkg_name = ? AND user_id=?",
new String[]{app.packageName, String.valueOf(app.userId)}); new String[]{app.packageName, String.valueOf(app.userId)}) > 0);
} }
public boolean disableModule(String packageName) { public boolean disableModule(String packageName) {
int mid = getModuleId(packageName); if (packageName.equals("lspd")) return false;
if (mid == -1) return false; boolean changed = executeInTransaction(() -> {
try {
db.beginTransaction();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("enabled", 0); values.put("enabled", 0);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)}); return db.update("modules", values, "module_pkg_name = ?", new String[]{packageName}) > 0;
} finally { });
db.setTransactionSuccessful(); if (changed) {
db.endTransaction();
}
// called by manager, should be async // called by manager, should be async
updateCaches(false); updateCaches(false);
return true; return true;
} else {
return false;
}
} }
public boolean enableModule(String packageName, ApplicationInfo info) { public boolean enableModule(String packageName, ApplicationInfo info) {
if (!updateModuleApkPath(packageName, getModuleApkPath(info), false)) return false; if (packageName.equals("lspd") || !updateModuleApkPath(packageName, getModuleApkPath(info), false))
int mid = getModuleId(packageName); return false;
if (mid == -1) return false; boolean changed = executeInTransaction(()->{
try {
db.beginTransaction();
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("enabled", 1); values.put("enabled", 1);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)}); return db.update("modules", values, "module_pkg_name = ?", new String[]{packageName}) > 0;
} finally { });
db.setTransactionSuccessful(); if (changed) {
db.endTransaction();
}
// Called by manager, should be async // Called by manager, should be async
updateCaches(false); updateCaches(false);
return true; return true;
} else {
return false;
}
} }
public void updateCache() { public void updateCache() {