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.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.zip.ZipFile;
public class ConfigManager {
private static ConfigManager instance = null;
private static final int DB_VERSION = 2;
private final SQLiteDatabase db =
SQLiteDatabase.openOrCreateDatabase(ConfigFileManager.dbPath, null);
@ -134,7 +136,11 @@ public class ConfigManager {
"mid integer," +
"app_pkg_name text 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 (" +
"module_pkg_name text NOT NULL," +
@ -142,7 +148,11 @@ public class ConfigManager {
"`group` text NOT NULL," +
"`key` text 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<>();
@ -276,18 +286,64 @@ public class ConfigManager {
cacheThread.start();
cacheHandler = new Handler(cacheThread.getLooper());
createTables();
initDB();
updateConfig();
// must ensure cache is valid for later usage
updateCaches(true);
}
private void createTables() {
private <T> T executeInTransaction(Supplier<T> execution) {
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();
createScopeTable.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("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) {
Log.e(TAG, "init db", e);
}
@ -381,6 +437,7 @@ public class ConfigManager {
while (cursor.moveToNext()) {
String packageName = cursor.getString(pkgNameIdx);
String apkPath = cursor.getString(apkPathIdx);
if (packageName.equals("lspd")) continue;
// if still present after removeIf, this package did not change.
var oldModule = cachedModule.get(packageName);
PackageInfo pkgInfo = null;
@ -556,10 +613,9 @@ public class ConfigManager {
@Nullable
public List<Application> getModuleScope(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return null;
if (packageName.equals("lspd")) return null;
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) {
return null;
}
@ -598,7 +654,7 @@ public class ConfigManager {
}
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()) {
Log.w(TAG, "update module apk path should not be called inside transaction");
return false;
@ -628,6 +684,7 @@ public class ConfigManager {
// Only be called before updating modules. No need to cache.
private int getModuleId(String packageName) {
if (packageName.equals("lspd")) return -1;
if (db.inTransaction()) {
Log.w(TAG, "get module id should not be called inside transaction");
return -1;
@ -648,8 +705,7 @@ public class ConfigManager {
self.packageName = packageName;
self.userId = 0;
scopes.add(self);
try {
db.beginTransaction();
executeInTransaction(() -> {
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
for (Application app : scopes) {
if (app.packageName.equals("android") && app.userId != 0) continue;
@ -659,10 +715,7 @@ public class ConfigManager {
values.put("user_id", app.userId);
db.insertWithOnConflict("scope", null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
});
// Called by manager, should be async
updateCaches(false);
return true;
@ -677,7 +730,9 @@ public class ConfigManager {
int modulePkgNameIdx = cursor.getColumnIndex("module_pkg_name");
HashSet<String> result = new HashSet<>();
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]);
}
@ -696,16 +751,8 @@ public class ConfigManager {
}
private boolean removeModuleWithoutCache(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return false;
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();
}
if (packageName.equals("lspd")) return false;
boolean res = executeInTransaction(() -> db.delete("modules", "module_pkg_name = ?", new String[]{packageName}) > 0);
try {
for (var user : UserService.getUsers()) {
removeModulePrefs(user.id, packageName);
@ -713,64 +760,56 @@ public class ConfigManager {
} catch (Throwable e) {
Log.w(TAG, "remove module prefs for " + packageName);
}
return true;
return res;
}
private void removeModuleScopeWithoutCache(Application module) {
int mid = getModuleId(module.packageName);
if (mid == -1) return;
try {
db.beginTransaction();
db.delete("scope", "mid = ? and user_id = ?", new String[]{String.valueOf(mid), String.valueOf(module.userId)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
private boolean removeModuleScopeWithoutCache(Application module) {
if (module.packageName.equals("lspd")) return false;
boolean res = executeInTransaction(() -> db.delete("scope", "mid = ? and user_id = ?", new String[]{module.packageName, String.valueOf(module.userId)}) > 0);
try {
removeModulePrefs(module.userId, module.packageName);
} catch (IOException e) {
Log.w(TAG, "removeModulePrefs", e);
}
return res;
}
private void removeAppWithoutCache(Application app) {
db.delete("scope", "app_pkg_name = ? AND user_id=?",
new String[]{app.packageName, String.valueOf(app.userId)});
private boolean removeAppWithoutCache(Application app) {
return executeInTransaction(() -> db.delete("scope", "app_pkg_name = ? AND user_id=?",
new String[]{app.packageName, String.valueOf(app.userId)}) > 0);
}
public boolean disableModule(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
if (packageName.equals("lspd")) return false;
boolean changed = executeInTransaction(() -> {
ContentValues values = new ContentValues();
values.put("enabled", 0);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
return db.update("modules", values, "module_pkg_name = ?", new String[]{packageName}) > 0;
});
if (changed) {
// called by manager, should be async
updateCaches(false);
return true;
} else {
return false;
}
}
public boolean enableModule(String packageName, ApplicationInfo info) {
if (!updateModuleApkPath(packageName, getModuleApkPath(info), false)) return false;
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
if (packageName.equals("lspd") || !updateModuleApkPath(packageName, getModuleApkPath(info), false))
return false;
boolean changed = executeInTransaction(()->{
ContentValues values = new ContentValues();
values.put("enabled", 1);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
return db.update("modules", values, "module_pkg_name = ?", new String[]{packageName}) > 0;
});
if (changed) {
// Called by manager, should be async
updateCaches(false);
return true;
} else {
return false;
}
}
public void updateCache() {