Config db to use cascade delete (#1408)
Co-authored-by: Howard Wu <40033067+Howard20181@users.noreply.github.com>
This commit is contained in:
parent
748e67e72b
commit
5656f1b330
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue