PackageManager: Support multi-source
This commit is contained in:
parent
9f9f856bf9
commit
23937764f9
@ -46,6 +46,10 @@ android {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@ -94,3 +94,6 @@
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
-keep class * extends io.neoterm.framework.database.annotation.* { *; }
|
||||
-keep interface * extends io.neoterm.framework.database.annotation.* { *; }
|
||||
|
||||
|
@ -7,16 +7,14 @@ package io.neoterm.component.pm
|
||||
enum class Architecture {
|
||||
ALL, ARM, AARCH64, X86, X86_64;
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
fun parse(arch: String): Architecture {
|
||||
when (arch) {
|
||||
"arm" -> return ARM
|
||||
"aarch64" -> return AARCH64
|
||||
"x86" -> return X86
|
||||
"x86_64" -> return X86_64
|
||||
else -> return ALL
|
||||
return when (arch) {
|
||||
"arm" -> ARM
|
||||
"aarch64" -> AARCH64
|
||||
"x86" -> X86
|
||||
"x86_64" -> X86_64
|
||||
else -> ALL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class PackageComponent implements NeoComponent {
|
||||
}
|
||||
|
||||
public HashMap<String, NeoPackageInfo> getPackages() {
|
||||
return queryEnabled ? neoPackages : new HashMap<String, NeoPackageInfo>();
|
||||
return queryEnabled ? neoPackages : new HashMap<>();
|
||||
}
|
||||
|
||||
public int getPackageCount() {
|
||||
|
29
app/src/main/java/io/neoterm/component/pm/Source.java
Normal file
29
app/src/main/java/io/neoterm/component/pm/Source.java
Normal file
@ -0,0 +1,29 @@
|
||||
package io.neoterm.component.pm;
|
||||
|
||||
import io.neoterm.framework.database.annotation.ID;
|
||||
import io.neoterm.framework.database.annotation.Table;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
@Table
|
||||
public class Source {
|
||||
@ID(autoIncrement = true)
|
||||
private int id;
|
||||
|
||||
public String url;
|
||||
|
||||
public String repo;
|
||||
|
||||
public boolean enabled;
|
||||
|
||||
public Source() {
|
||||
// for Database
|
||||
}
|
||||
|
||||
public Source(String url, String repo, boolean enabled) {
|
||||
this.url = url;
|
||||
this.repo = repo;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
73
app/src/main/java/io/neoterm/component/pm/SourceHelper.kt
Normal file
73
app/src/main/java/io/neoterm/component/pm/SourceHelper.kt
Normal file
@ -0,0 +1,73 @@
|
||||
package io.neoterm.component.pm
|
||||
|
||||
import io.neoterm.frontend.component.ComponentManager
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
import io.neoterm.frontend.logging.NLog
|
||||
import io.neoterm.utils.FileUtils
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
object SourceHelper {
|
||||
fun syncSource() {
|
||||
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||
syncSource(sourceManager)
|
||||
}
|
||||
|
||||
fun syncSource(sourceManager: SourceManager) {
|
||||
val sourceFile = File(NeoTermPath.SOURCE_FILE)
|
||||
val content = buildString {
|
||||
this.append("# Generated by NeoTerm-Preference\n")
|
||||
sourceManager.getEnabledSources()
|
||||
.joinTo(this, "\n") { "deb ${it.url} ${it.repo}\n" }
|
||||
}
|
||||
FileUtils.writeFile(sourceFile, content.toByteArray())
|
||||
}
|
||||
|
||||
fun detectSourceFiles(): List<File> {
|
||||
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||
val sourceFiles = ArrayList<File>()
|
||||
try {
|
||||
val prefixes = sourceManager.getEnabledSources()
|
||||
.map { detectSourceFilePrefix(it) }
|
||||
.filter { it.isNotEmpty() }
|
||||
|
||||
File(NeoTermPath.PACKAGE_LIST_DIR)
|
||||
.listFiles()
|
||||
.filterTo(sourceFiles, { file ->
|
||||
prefixes.filter { file.name.startsWith(it) }
|
||||
.count() > 0
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
sourceFiles.clear()
|
||||
NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}")
|
||||
}
|
||||
|
||||
return sourceFiles
|
||||
}
|
||||
|
||||
fun detectSourceFilePrefix(source: Source): String {
|
||||
try {
|
||||
val url = URL(source.url)
|
||||
val builder = StringBuilder(url.host)
|
||||
if (url.port != -1) {
|
||||
builder.append(":${url.port}")
|
||||
}
|
||||
|
||||
val path = url.path
|
||||
if (path != null && path.isNotEmpty()) {
|
||||
builder.append("_")
|
||||
val fixedPath = path.replace("/", "_")
|
||||
.substring(1) // skip the last '/'
|
||||
builder.append(fixedPath)
|
||||
}
|
||||
builder.append("_dists_${source.repo.replace(" ".toRegex(), "_")}_binary-")
|
||||
return builder.toString()
|
||||
} catch (e: Exception) {
|
||||
NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
@ -2,26 +2,53 @@ package io.neoterm.component.pm
|
||||
|
||||
import io.neoterm.App
|
||||
import io.neoterm.R
|
||||
import io.neoterm.frontend.config.NeoPreference
|
||||
import io.neoterm.framework.NeoTermDatabase
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
class SourceManager internal constructor() {
|
||||
val sources = mutableSetOf<String>()
|
||||
private val database = NeoTermDatabase.instance("sources")
|
||||
|
||||
init {
|
||||
NeoPreference.loadStrings(NeoPreference.KEY_SOURCES).mapTo(sources, { it })
|
||||
if (sources.isEmpty()) {
|
||||
sources.addAll(App.get().resources.getStringArray(R.array.pref_package_source_values))
|
||||
if (database.findAll<Source>(Source::class.java).isEmpty()) {
|
||||
App.get().resources.getStringArray(R.array.pref_package_source_values)
|
||||
.forEach {
|
||||
database.saveBean(Source(it, "stable main", true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addSource(sourceUrl: String) {
|
||||
sources.add(sourceUrl)
|
||||
fun addSource(sourceUrl: String, repo: String, enabled: Boolean) {
|
||||
database.saveBean(Source(sourceUrl, repo, enabled))
|
||||
}
|
||||
|
||||
fun removeSource(sourceUrl: String) {
|
||||
database.deleteBeanByWhere(Source::class.java, "url == '$sourceUrl'")
|
||||
}
|
||||
|
||||
fun updateAll(sources: List<Source>) {
|
||||
database.dropAllTable()
|
||||
database.saveBeans(sources)
|
||||
}
|
||||
|
||||
fun getAllSources(): List<Source> {
|
||||
return database.findAll<Source>(Source::class.java)
|
||||
}
|
||||
|
||||
fun getEnabledSources(): List<Source> {
|
||||
return getAllSources().filter { it.enabled }
|
||||
}
|
||||
|
||||
fun getMainPackageSource(): String {
|
||||
return getEnabledSources()
|
||||
.map { it.repo }
|
||||
.singleOrNull { it.trim() == "stable main" }
|
||||
?: NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE
|
||||
}
|
||||
|
||||
fun applyChanges() {
|
||||
NeoPreference.storeStrings(NeoPreference.KEY_SOURCES, sources)
|
||||
database.vacuum()
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
package io.neoterm.component.pm
|
||||
|
||||
import io.neoterm.R
|
||||
import io.neoterm.frontend.config.NeoPreference
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
import io.neoterm.frontend.logging.NLog
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
object SourceUtils {
|
||||
|
||||
fun detectSourceFiles(): ArrayList<File> {
|
||||
val sourceFiles = ArrayList<File>()
|
||||
try {
|
||||
val sourceUrl = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
||||
val packageFilePrefix = detectSourceFilePrefix(sourceUrl)
|
||||
if (packageFilePrefix.isNotEmpty()) {
|
||||
File(NeoTermPath.PACKAGE_LIST_DIR)
|
||||
.listFiles()
|
||||
.filterTo(sourceFiles) { it.name.startsWith(packageFilePrefix) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sourceFiles.clear()
|
||||
NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}")
|
||||
}
|
||||
|
||||
return sourceFiles
|
||||
}
|
||||
|
||||
fun detectSourceFilePrefix(sourceUrl: String): String {
|
||||
try {
|
||||
val url = URL(sourceUrl)
|
||||
val builder = StringBuilder()
|
||||
builder.append(url.host)
|
||||
// https://github.com/NeoTerm/NeoTerm/issues/1
|
||||
if (url.port != -1) {
|
||||
builder.append(":${url.port}")
|
||||
}
|
||||
|
||||
val path = url.path
|
||||
if (path != null && path.isNotEmpty()) {
|
||||
builder.append("_")
|
||||
val fixedPath = path.replace("/", "_")
|
||||
.substring(1) // skip the last '/'
|
||||
builder.append(fixedPath)
|
||||
}
|
||||
builder.append("_dists_stable_main_binary-")
|
||||
return builder.toString()
|
||||
} catch (e: Exception) {
|
||||
NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
675
app/src/main/java/io/neoterm/framework/NeoTermDatabase.java
Normal file
675
app/src/main/java/io/neoterm/framework/NeoTermDatabase.java
Normal file
@ -0,0 +1,675 @@
|
||||
package io.neoterm.framework;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.neoterm.App;
|
||||
import io.neoterm.framework.database.DatabaseDataType;
|
||||
import io.neoterm.framework.database.OnDatabaseUpgradedListener;
|
||||
import io.neoterm.framework.database.SQLStatementHelper;
|
||||
import io.neoterm.framework.database.SQLTypeParser;
|
||||
import io.neoterm.framework.database.NeoTermSQLiteConfig;
|
||||
import io.neoterm.framework.database.TableHelper;
|
||||
import io.neoterm.framework.database.ValueHelper;
|
||||
import io.neoterm.framework.database.bean.TableInfo;
|
||||
import io.neoterm.framework.reflection.Reflect;
|
||||
import io.neoterm.frontend.logging.NLog;
|
||||
|
||||
/**
|
||||
* @author Lody, Kiva
|
||||
* <p>
|
||||
* 基于<b>DTO(DataToObject)</b>映射的数据库操纵模型.
|
||||
* 通过少量可选的注解,即可构造数据模型.
|
||||
* 增删查改异常轻松.
|
||||
* @version 1.4
|
||||
*/
|
||||
public class NeoTermDatabase {
|
||||
|
||||
/**
|
||||
* 缓存创建的数据库,以便防止数据库冲突.
|
||||
*/
|
||||
private static final Map<String, NeoTermDatabase> DAO_MAP = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 数据库配置
|
||||
*/
|
||||
private NeoTermSQLiteConfig neoTermSQLiteConfig;
|
||||
/**
|
||||
* 内部操纵的数据库执行类
|
||||
*/
|
||||
private SQLiteDatabase db;
|
||||
|
||||
/**
|
||||
* 默认构造器
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
private NeoTermDatabase(NeoTermSQLiteConfig config) {
|
||||
|
||||
this.neoTermSQLiteConfig = config;
|
||||
String saveDir = config.getSaveDir();
|
||||
if (saveDir != null
|
||||
&& saveDir.trim().length() > 0) {
|
||||
this.db = createDataBaseFileOnSDCard(saveDir,
|
||||
config.getDatabaseName());
|
||||
} else {
|
||||
this.db = new SQLiteDataBaseHelper(App.Companion.get()
|
||||
.getApplicationContext()
|
||||
.getApplicationContext(), config)
|
||||
.getWritableDatabase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置取得用于操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(NeoTermSQLiteConfig config) {
|
||||
if (config.getDatabaseName() == null) {
|
||||
throw new IllegalArgumentException("DBName is null in SqLiteConfig.");
|
||||
}
|
||||
NeoTermDatabase dao = DAO_MAP.get(config.getDatabaseName());
|
||||
if (dao == null) {
|
||||
dao = new NeoTermDatabase(config);
|
||||
synchronized (DAO_MAP) {
|
||||
DAO_MAP.put(config.getDatabaseName(), dao);
|
||||
}
|
||||
} else {//更换配置
|
||||
dao.applyConfig(config);
|
||||
}
|
||||
|
||||
return dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据默认配置取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance() {
|
||||
return instance(NeoTermSQLiteConfig.DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param dbName
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(String dbName) {
|
||||
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||
config.setDatabaseName(dbName);
|
||||
return instance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param dbVersion
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(int dbVersion) {
|
||||
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||
config.setDatabaseVersion(dbVersion);
|
||||
return instance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(OnDatabaseUpgradedListener listener) {
|
||||
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||
config.setOnDatabaseUpgradedListener(listener);
|
||||
return instance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param dbName
|
||||
* @param dbVersion
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(String dbName, int dbVersion) {
|
||||
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||
config.setDatabaseName(dbName);
|
||||
config.setDatabaseVersion(dbVersion);
|
||||
return instance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得操纵数据库的WeLikeDao实例
|
||||
*
|
||||
* @param dbName
|
||||
* @param dbVersion
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public static NeoTermDatabase instance(String dbName, int dbVersion, OnDatabaseUpgradedListener listener) {
|
||||
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||
config.setDatabaseName(dbName);
|
||||
config.setDatabaseVersion(dbVersion);
|
||||
config.setOnDatabaseUpgradedListener(listener);
|
||||
return instance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置为新的参数(不改变数据库名).
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
private void applyConfig(NeoTermSQLiteConfig config) {
|
||||
this.neoTermSQLiteConfig.debugMode = config.debugMode;
|
||||
this.neoTermSQLiteConfig.setOnDatabaseUpgradedListener(config.getOnDatabaseUpgradedListener());
|
||||
}
|
||||
|
||||
public void release() {
|
||||
DAO_MAP.clear();
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.d("缓存的DAO已经全部清除,将不占用内存.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在SD卡的指定目录上创建数据库文件
|
||||
*
|
||||
* @param sdcardPath sd卡路径
|
||||
* @param dbFileName 数据库文件名
|
||||
* @return
|
||||
*/
|
||||
private SQLiteDatabase createDataBaseFileOnSDCard(String sdcardPath,
|
||||
String dbFileName) {
|
||||
File dbFile = new File(sdcardPath, dbFileName);
|
||||
if (!dbFile.exists()) {
|
||||
try {
|
||||
if (dbFile.createNewFile()) {
|
||||
return SQLiteDatabase.openOrCreateDatabase(dbFile, null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("无法在 " + dbFile.getAbsolutePath() + "创建DB文件.");
|
||||
}
|
||||
} else {
|
||||
//数据库文件已经存在,无需再次创建.
|
||||
return SQLiteDatabase.openOrCreateDatabase(dbFile, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果表不存在,需要创建它.
|
||||
*
|
||||
* @param clazz
|
||||
*/
|
||||
private void createTableIfNeed(Class<?> clazz) {
|
||||
TableInfo tableInfo = TableHelper.from(clazz);
|
||||
if (tableInfo.isCreate) {
|
||||
return;
|
||||
}
|
||||
if (!isTableExist(tableInfo)) {
|
||||
String sql = SQLStatementHelper.createTable(tableInfo);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(sql);
|
||||
}
|
||||
db.execSQL(sql);
|
||||
Method afterTableCreateMethod = tableInfo.afterTableCreateMethod;
|
||||
if (afterTableCreateMethod != null) {
|
||||
//如果afterTableMethod存在,就调用它
|
||||
try {
|
||||
afterTableCreateMethod.invoke(null, this);
|
||||
} catch (Throwable ignore) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断表是否存在?
|
||||
*
|
||||
* @param table 需要盘的的表
|
||||
* @return
|
||||
*/
|
||||
private boolean isTableExist(TableInfo table) {
|
||||
String sql = "SELECT COUNT(*) AS c FROM sqlite_master WHERE type ='table' AND name ='"
|
||||
+ table.tableName + "' ";
|
||||
try (Cursor cursor = db.rawQuery(sql, null)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
int count = cursor.getInt(0);
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除全部的表
|
||||
*/
|
||||
public void dropAllTable() {
|
||||
try (Cursor cursor = db.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
while (cursor.moveToNext()) {
|
||||
try {
|
||||
dropTable(cursor.getString(0));
|
||||
} catch (SQLException ignore) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库中的表的数量
|
||||
*
|
||||
* @return 表的数量
|
||||
*/
|
||||
public int tableCount() {
|
||||
try (Cursor cursor = db.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||
return cursor == null ? 0 : cursor.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库中的所有表名组成的List.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<String> getTableList() {
|
||||
try (Cursor cursor = db.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||
List<String> tableList = new ArrayList<>();
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
while (cursor.moveToNext()) {
|
||||
tableList.add(cursor.getString(0));
|
||||
}
|
||||
}
|
||||
return tableList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一张表
|
||||
*
|
||||
* @param beanClass 表所对应的类
|
||||
*/
|
||||
public void dropTable(Class<?> beanClass) {
|
||||
TableInfo tableInfo = TableHelper.from(beanClass);
|
||||
dropTable(tableInfo.tableName);
|
||||
tableInfo.isCreate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一张表
|
||||
*
|
||||
* @param tableName 表名
|
||||
*/
|
||||
public void dropTable(String tableName) {
|
||||
String statement = "DROP TABLE IF EXISTS " + tableName;
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
db.execSQL(statement);
|
||||
TableInfo tableInfo = TableHelper.findTableInfoByName(tableName);
|
||||
if (tableInfo != null) {
|
||||
tableInfo.isCreate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储一个Bean.
|
||||
*
|
||||
* @param bean
|
||||
* @return
|
||||
*/
|
||||
public <T> NeoTermDatabase saveBean(T bean) {
|
||||
createTableIfNeed(bean.getClass());
|
||||
String statement = SQLStatementHelper.insertIntoTable(bean);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
db.execSQL(statement);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储多个Bean.
|
||||
*
|
||||
* @param beans
|
||||
* @return
|
||||
*/
|
||||
public NeoTermDatabase saveBeans(Object[] beans) {
|
||||
for (Object o : beans) {
|
||||
saveBean(o);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储多个Bean.
|
||||
*
|
||||
* @param beans
|
||||
* @return
|
||||
*/
|
||||
public <T> NeoTermDatabase saveBeans(List<T> beans) {
|
||||
for (Object o : beans) {
|
||||
saveBean(o);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 寻找Bean对应的全部数据
|
||||
*
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> List<T> findAll(Class<?> clazz) {
|
||||
createTableIfNeed(clazz);
|
||||
TableInfo tableInfo = TableHelper.from(clazz);
|
||||
String statement = SQLStatementHelper.selectTable(tableInfo.tableName);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
List<T> list = new ArrayList<T>();
|
||||
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||
if (cursor == null) {
|
||||
// DO NOT RETURN NULL
|
||||
// null checks are ugly!
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
T object = Reflect.on(clazz).create().get();
|
||||
|
||||
if (tableInfo.containID) {
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||
String idFieldName = tableInfo.primaryField.getName();
|
||||
ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName));
|
||||
}
|
||||
|
||||
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||
ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName()));
|
||||
}
|
||||
list.add(object);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据where语句寻找Bean
|
||||
*
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> List<T> findBeanByWhere(Class<?> clazz, String where) {
|
||||
createTableIfNeed(clazz);
|
||||
TableInfo tableInfo = TableHelper.from(clazz);
|
||||
String statement = SQLStatementHelper.findByWhere(tableInfo, where);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
List<T> list = new ArrayList<>();
|
||||
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||
if (cursor == null) {
|
||||
// DO NOT RETURN NULL
|
||||
// null checks are ugly!
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
T object = Reflect.on(clazz).create().get();
|
||||
if (tableInfo.containID) {
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||
String idFieldName = tableInfo.primaryField.getName();
|
||||
ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName));
|
||||
}
|
||||
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||
ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName()));
|
||||
}
|
||||
list.add(object);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T findOneBeanByWhere(Class<?> clazz, String where) {
|
||||
List<T> list = findBeanByWhere(clazz, where);
|
||||
if (!list.isEmpty()) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据where语句删除Bean
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public NeoTermDatabase deleteBeanByWhere(Class<?> clazz, String where) {
|
||||
createTableIfNeed(clazz);
|
||||
TableInfo tableInfo = TableHelper.from(clazz);
|
||||
String statement = SQLStatementHelper.deleteByWhere(tableInfo, where);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
try {
|
||||
db.execSQL(statement);
|
||||
} catch (SQLException ignore) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定ID的bean
|
||||
*
|
||||
* @param tableClass
|
||||
* @param id
|
||||
* @return 删除的Bean
|
||||
*/
|
||||
public NeoTermDatabase deleteBeanByID(Class<?> tableClass, Object id) {
|
||||
createTableIfNeed(tableClass);
|
||||
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass());
|
||||
if (dataType != null && tableInfo.primaryField != null) {
|
||||
//判断ID类型是否与数据类型匹配
|
||||
boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType);
|
||||
if (!match) {//不匹配,抛出异常
|
||||
throw new IllegalArgumentException("类型 " + id.getClass().getName() + " 不是主键的类型,主键的类型应该为 " + tableInfo.primaryField.getType().getName());
|
||||
}
|
||||
}
|
||||
String idValue = ValueHelper.valueToString(dataType, id);
|
||||
String statement = SQLStatementHelper.deleteByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
|
||||
try {
|
||||
db.execSQL(statement);
|
||||
} catch (SQLException ignore) {
|
||||
ignore.printStackTrace();
|
||||
//删除失败
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的where更新数据
|
||||
*
|
||||
* @param tableClass
|
||||
* @param where
|
||||
* @param bean
|
||||
* @return
|
||||
*/
|
||||
public NeoTermDatabase updateByWhere(Class<?> tableClass, String where, Object bean) {
|
||||
createTableIfNeed(tableClass);
|
||||
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||
String statement = SQLStatementHelper.updateByWhere(tableInfo, bean, where);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.d(statement);
|
||||
}
|
||||
db.execSQL(statement);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的id更新数据
|
||||
*
|
||||
* @param tableClass
|
||||
* @param id
|
||||
* @param bean
|
||||
* @return
|
||||
*/
|
||||
public NeoTermDatabase updateByID(Class<?> tableClass, Object id, Object bean) {
|
||||
createTableIfNeed(tableClass);
|
||||
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||
StringBuilder subStatement = new StringBuilder();
|
||||
if (tableInfo.containID) {
|
||||
subStatement.append(tableInfo.primaryField.getName()).append(" = ").append(ValueHelper.valueToString(SQLTypeParser.getDataType(tableInfo.primaryField), id));
|
||||
} else {
|
||||
subStatement.append("_id = ").append((int) id);
|
||||
}
|
||||
updateByWhere(tableClass, subStatement.toString(), bean);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找Bean
|
||||
*
|
||||
* @param tableClass
|
||||
* @param id
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> T findBeanByID(Class<?> tableClass, Object id) {
|
||||
createTableIfNeed(tableClass);
|
||||
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass());
|
||||
if (dataType == null) {
|
||||
return null;
|
||||
}
|
||||
// 判断ID类型是否与数据类型匹配
|
||||
boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType) || tableInfo.primaryField == null;
|
||||
if (!match) {// 不匹配,抛出异常
|
||||
throw new IllegalArgumentException("Type " + id.getClass().getName() + " is not the primary key, expecting " + tableInfo.primaryField.getType().getName());
|
||||
}
|
||||
String idValue = ValueHelper.valueToString(dataType, id);
|
||||
String statement = SQLStatementHelper.findByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue);
|
||||
if (neoTermSQLiteConfig.debugMode) {
|
||||
NLog.INSTANCE.w(statement);
|
||||
}
|
||||
|
||||
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||
if (cursor != null && cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
T bean = Reflect.on(tableClass).create().get();
|
||||
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
DatabaseDataType fieldType = tableInfo.fieldToDataTypeMap.get(field);
|
||||
ValueHelper.setKeyValue(cursor, bean, field, fieldType, cursor.getColumnIndex(field.getName()));
|
||||
}
|
||||
try {
|
||||
Reflect.on(bean).set(tableInfo.containID ? tableInfo.primaryField.getName() : "_id", id);
|
||||
} catch (Throwable ignore) {
|
||||
// 我们允许Bean没有id字段,因此此异常可以忽略
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 VACUUM 命令压缩数据库
|
||||
*/
|
||||
public void vacuum() {
|
||||
db.execSQL("VACUUM");
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用本方法会释放当前数据库占用的内存,
|
||||
* 调用后请确保你不会在接下来的代码中继续用到本实例.
|
||||
*/
|
||||
public void destroy() {
|
||||
DAO_MAP.remove(this);
|
||||
this.neoTermSQLiteConfig = null;
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得内部操纵的SqliteDatabase.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SQLiteDatabase getDatabase() {
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部数据库监听器,负责派发接口.
|
||||
*/
|
||||
private class SQLiteDataBaseHelper extends SQLiteOpenHelper {
|
||||
private final OnDatabaseUpgradedListener onDatabaseUpgradedListener;
|
||||
private final boolean defaultDropAllTables;
|
||||
|
||||
public SQLiteDataBaseHelper(Context context, NeoTermSQLiteConfig config) {
|
||||
super(context, config.getDatabaseName(), null, config.getDatabaseVersion());
|
||||
this.onDatabaseUpgradedListener = config.getOnDatabaseUpgradedListener();
|
||||
this.defaultDropAllTables = config.isDefaultDropAllTables();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (onDatabaseUpgradedListener != null) {
|
||||
onDatabaseUpgradedListener.onDatabaseUpgraded(db, oldVersion, newVersion);
|
||||
|
||||
} else if (defaultDropAllTables) { // 干掉所有的表
|
||||
dropAllTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public enum DatabaseDataType {
|
||||
/**
|
||||
* int类型
|
||||
*/
|
||||
INTEGER,
|
||||
/**
|
||||
* String类型
|
||||
*/
|
||||
TEXT,
|
||||
/**
|
||||
* float类型
|
||||
*/
|
||||
FLOAT,
|
||||
/**
|
||||
* long类型
|
||||
*/
|
||||
BIGINT,
|
||||
/**
|
||||
* double类型
|
||||
*/
|
||||
DOUBLE;
|
||||
|
||||
boolean nullable = true;
|
||||
|
||||
/**
|
||||
* 数据类型是否允许为null
|
||||
*/
|
||||
public DatabaseDataType nullable(boolean nullable) {
|
||||
this.nullable = nullable;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class NeoTermSQLiteConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -4069725570156436316L;
|
||||
//==============================================================
|
||||
// 常量
|
||||
//==============================================================
|
||||
public static String DEFAULT_DB_NAME = "we_like.db";
|
||||
public static NeoTermSQLiteConfig DEFAULT_CONFIG = new NeoTermSQLiteConfig();
|
||||
|
||||
//==============================================================
|
||||
// 字段
|
||||
//==============================================================
|
||||
/**
|
||||
* 是否为DEBUG模式
|
||||
*/
|
||||
public boolean debugMode = false;
|
||||
/**
|
||||
* 数据库名
|
||||
*/
|
||||
private String dbName = DEFAULT_DB_NAME;
|
||||
/**
|
||||
* 数据库升级监听器
|
||||
*/
|
||||
private OnDatabaseUpgradedListener onDatabaseUpgradedListener;
|
||||
private boolean defaultDropAllTables = false;
|
||||
private String saveDir;
|
||||
private int dbVersion = 1;
|
||||
|
||||
/**
|
||||
* 取得数据库的名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getDatabaseName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据库的名称
|
||||
*
|
||||
* @param dbName
|
||||
*/
|
||||
public void setDatabaseName(String dbName) {
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库升级监听器
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public OnDatabaseUpgradedListener getOnDatabaseUpgradedListener() {
|
||||
return onDatabaseUpgradedListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据库升级监听器
|
||||
*
|
||||
* @param onDatabaseUpgradedListener
|
||||
*/
|
||||
public void setOnDatabaseUpgradedListener(OnDatabaseUpgradedListener onDatabaseUpgradedListener) {
|
||||
this.onDatabaseUpgradedListener = onDatabaseUpgradedListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库保存目录
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getSaveDir() {
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据库的保存目录
|
||||
*
|
||||
* @param saveDir
|
||||
*/
|
||||
public void setSaveDir(String saveDir) {
|
||||
this.saveDir = saveDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DB的版本号
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getDatabaseVersion() {
|
||||
return dbVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置DB的版本号
|
||||
*
|
||||
* @param dbVersion
|
||||
*/
|
||||
public void setDatabaseVersion(int dbVersion) {
|
||||
this.dbVersion = dbVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* App 更新时是否默认删除所有存在的表
|
||||
* @return
|
||||
*/
|
||||
public boolean isDefaultDropAllTables() {
|
||||
return defaultDropAllTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 App 更新时是否默认删除所有存在的表
|
||||
* @param defaultDropAllTables
|
||||
*/
|
||||
public void setDefaultDropAllTables(boolean defaultDropAllTables) {
|
||||
this.defaultDropAllTables = defaultDropAllTables;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public interface OnDatabaseUpgradedListener {
|
||||
/**
|
||||
* @param db 数据库
|
||||
* @param oldVersion 旧版本
|
||||
* @param newVersion 新版本
|
||||
*/
|
||||
void onDatabaseUpgraded(SQLiteDatabase db, int oldVersion, int newVersion);
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import io.neoterm.framework.database.annotation.ID;
|
||||
import io.neoterm.framework.database.bean.TableInfo;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class SQLStatementHelper {
|
||||
|
||||
/**
|
||||
* 构造<b>创建表</b>的语句
|
||||
*
|
||||
* @param tableInfo 表信息
|
||||
* @return 创建表的SQL语句
|
||||
*/
|
||||
public static String createTable(TableInfo tableInfo) {
|
||||
StringBuilder statement = new StringBuilder();
|
||||
|
||||
statement.append("CREATE TABLE ").append("'")
|
||||
.append(tableInfo.tableName).append("'")
|
||||
.append(" (");
|
||||
|
||||
if (tableInfo.containID) {
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||
if (dataType == null) {
|
||||
throw new IllegalArgumentException("Type of " + tableInfo.primaryField.getType().getName() + " is not support in WelikeDB.");
|
||||
}
|
||||
statement.append("'").append(tableInfo.primaryField.getName()).append("'");
|
||||
switch (dataType) {
|
||||
case INTEGER:
|
||||
statement.append(" INTEGER PRIMARY KEY ");
|
||||
ID id = tableInfo.primaryField.getAnnotation(ID.class);
|
||||
if (id != null && id.autoIncrement()) {
|
||||
statement.append("AUTOINCREMENT");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
statement
|
||||
.append(" ")
|
||||
.append(dataType.name())
|
||||
.append(" PRIMARY KEY");
|
||||
}
|
||||
|
||||
statement.append(",");
|
||||
|
||||
|
||||
} else {
|
||||
statement.append("'_id' INTEGER PRIMARY KEY AUTOINCREMENT,");
|
||||
}
|
||||
|
||||
|
||||
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||
statement.append("'").append(field.getName()).append("'")
|
||||
.append(" ")
|
||||
.append(dataType.name());
|
||||
if (!dataType.nullable) {
|
||||
statement.append(" NOT NULL");
|
||||
}
|
||||
statement.append(",");
|
||||
}
|
||||
//删掉最后一个逗号
|
||||
statement.deleteCharAt(statement.length() - 1);
|
||||
statement.append(")");
|
||||
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 插入一个Bean 的语句.
|
||||
*
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
public static String insertIntoTable(Object o) {
|
||||
TableInfo tableInfo = TableHelper.from(o.getClass());
|
||||
StringBuilder statement = new StringBuilder();
|
||||
statement.append("INSERT INTO ").append(tableInfo.tableName).append(" ");
|
||||
statement.append("VALUES(");
|
||||
|
||||
if (tableInfo.containID) {
|
||||
DatabaseDataType primaryDataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||
switch (primaryDataType) {
|
||||
case INTEGER:
|
||||
statement.append("NULL,");
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
statement
|
||||
.append(ValueHelper.valueToString(primaryDataType, tableInfo.primaryField, o))
|
||||
.append(",");
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
statement.append("NULL,");
|
||||
}
|
||||
|
||||
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||
try {
|
||||
statement.append(ValueHelper.valueToString(dataType, field, o)).append(",");
|
||||
} catch (IllegalAccessException e) {
|
||||
//不会发生...
|
||||
}
|
||||
}
|
||||
statement.deleteCharAt(statement.length() - 1);
|
||||
statement.append(")");
|
||||
|
||||
return statement.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据where条件创建选择语句
|
||||
*
|
||||
* @param tableInfo
|
||||
* @param where
|
||||
* @return
|
||||
*/
|
||||
public static String findByWhere(TableInfo tableInfo, String where) {
|
||||
StringBuilder statement = new StringBuilder("SELECT * FROM ");
|
||||
statement
|
||||
.append(tableInfo.tableName)
|
||||
.append(" ")
|
||||
.append("WHERE ")
|
||||
.append(where);
|
||||
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据where条件创建删除语句
|
||||
*
|
||||
* @param tableInfo
|
||||
* @param where
|
||||
* @return
|
||||
*/
|
||||
public static String deleteByWhere(TableInfo tableInfo, String where) {
|
||||
StringBuilder statement = new StringBuilder("DELETE FROM ");
|
||||
statement
|
||||
.append(tableInfo.tableName)
|
||||
.append(" ")
|
||||
.append("WHERE ")
|
||||
.append(where);
|
||||
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据where条件创建更新语句
|
||||
*
|
||||
* @param tableInfo
|
||||
* @param bean
|
||||
* @param where
|
||||
* @return
|
||||
*/
|
||||
public static String updateByWhere(TableInfo tableInfo, Object bean, String where) {
|
||||
StringBuilder builder = new StringBuilder("UPDATE ");
|
||||
|
||||
builder.append(tableInfo.tableName).append(" SET ");
|
||||
|
||||
for (Field f : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||
|
||||
try {
|
||||
builder.append(f.getName())
|
||||
.append(" = ")
|
||||
.append(ValueHelper.valueToString(
|
||||
SQLTypeParser.getDataType(f.getType()),
|
||||
f.get(bean))).append(",");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
builder.deleteCharAt(builder.length() - 1);//删除最后一个逗号
|
||||
|
||||
builder.append(" WHERE ");
|
||||
builder.append(where);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建选中table的语句
|
||||
*
|
||||
* @param tableName
|
||||
* @return
|
||||
*/
|
||||
public static String selectTable(String tableName) {
|
||||
return "SELECT * FROM " + tableName;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import io.neoterm.framework.database.annotation.Ignore;
|
||||
import io.neoterm.framework.database.annotation.NotNull;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class SQLTypeParser {
|
||||
/**
|
||||
* 根据字段类型匹配它在数据库中的对应类型.
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
public static DatabaseDataType getDataType(Field field) {
|
||||
Class<?> clazz = field.getType();
|
||||
if (clazz == (String.class)) {
|
||||
return DatabaseDataType.TEXT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
} else if (clazz == (int.class) || clazz == (Integer.class)) {
|
||||
return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
} else if (clazz == (float.class) || clazz == (Float.class)) {
|
||||
return DatabaseDataType.FLOAT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
} else if (clazz == (long.class) || clazz == (Long.class)) {
|
||||
return DatabaseDataType.BIGINT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
} else if (clazz == (double.class) || clazz == (Double.class)) {
|
||||
return DatabaseDataType.DOUBLE.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
} else if (clazz == (boolean.class) || clazz == (Boolean.class)) {
|
||||
return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段类型匹配它在数据库中的对应类型.
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public static DatabaseDataType getDataType(Class<?> clazz) {
|
||||
if (clazz == (String.class)) {
|
||||
return DatabaseDataType.TEXT;
|
||||
} else if (clazz == (int.class) || clazz == (Integer.class)) {
|
||||
return DatabaseDataType.INTEGER;
|
||||
} else if (clazz == (float.class) || clazz == (Float.class)) {
|
||||
return DatabaseDataType.FLOAT;
|
||||
} else if (clazz == (long.class) || clazz == (Long.class)) {
|
||||
return DatabaseDataType.BIGINT;
|
||||
} else if (clazz == (double.class) || clazz == (Double.class)) {
|
||||
return DatabaseDataType.DOUBLE;
|
||||
} else if (clazz == (boolean.class) || clazz == (Boolean.class)) {
|
||||
return DatabaseDataType.INTEGER;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段类型与数据类型是否匹配?
|
||||
*
|
||||
* @param field
|
||||
* @param dataType
|
||||
* @return
|
||||
*/
|
||||
public static boolean matchType(Field field, DatabaseDataType dataType) {
|
||||
DatabaseDataType fieldDataType = getDataType(field.getType());
|
||||
|
||||
return dataType != null && fieldDataType == (dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段是否可以被数据库忽略?
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
public static boolean isIgnore(Field field) {
|
||||
return field.getAnnotation(Ignore.class) != null;
|
||||
}
|
||||
}
|
132
app/src/main/java/io/neoterm/framework/database/TableHelper.java
Normal file
132
app/src/main/java/io/neoterm/framework/database/TableHelper.java
Normal file
@ -0,0 +1,132 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.neoterm.framework.NeoTermDatabase;
|
||||
import io.neoterm.framework.database.annotation.ID;
|
||||
import io.neoterm.framework.database.annotation.Table;
|
||||
import io.neoterm.framework.database.bean.TableInfo;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class TableHelper {
|
||||
private static final Map<Class<?>, TableInfo> classToTableInfoMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 根据传入的Bean的Class将其映射为一个TableInfo.
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public static TableInfo from(Class<?> clazz) {
|
||||
TableInfo tableInfo = classToTableInfoMap.get(clazz);
|
||||
if (tableInfo != null) {
|
||||
return tableInfo;
|
||||
}
|
||||
tableInfo = new TableInfo();
|
||||
//Table注解解析
|
||||
Table table = clazz.getAnnotation(Table.class);
|
||||
String afterTableCreateMethod = table != null ? table.afterTableCreate() : null;
|
||||
if (afterTableCreateMethod != null && afterTableCreateMethod.trim().length() > 0) {
|
||||
try {
|
||||
Method method = clazz.getDeclaredMethod(afterTableCreateMethod, NeoTermDatabase.class);
|
||||
if (method != null && Modifier.isStatic(method.getModifiers())) {
|
||||
method.setAccessible(true);
|
||||
tableInfo.afterTableCreateMethod = method;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
if (table != null && table.name().trim().length() != 0) {
|
||||
tableInfo.tableName = table.name();
|
||||
} else {
|
||||
tableInfo.tableName = clazz.getName().replace(".", "_");
|
||||
}
|
||||
|
||||
Map<Field, DatabaseDataType> fieldEnumMap = new HashMap<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
//如果这个字段加了ignore注解,我们就跳过
|
||||
if (SQLTypeParser.isIgnore(field)) {
|
||||
continue;
|
||||
}
|
||||
DatabaseDataType dataType = SQLTypeParser.getDataType(field);
|
||||
if (dataType != null) {
|
||||
fieldEnumMap.put(field, dataType);
|
||||
} else {
|
||||
throw new IllegalArgumentException("The type of " + field.getName() + " is not supported in database.");
|
||||
}
|
||||
}
|
||||
|
||||
tableInfo.fieldToDataTypeMap = fieldEnumMap;
|
||||
buildPrimaryIDForTableInfo(tableInfo);
|
||||
tableInfo.createTableStatement = SQLStatementHelper.createTable(tableInfo);
|
||||
|
||||
synchronized (classToTableInfoMap) {
|
||||
classToTableInfoMap.put(clazz, tableInfo);
|
||||
}
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为一个Bean匹配一个ID字段,如果ID字段不存在,使用默认的_id替代.
|
||||
*
|
||||
* @param info
|
||||
* @return
|
||||
*/
|
||||
private static TableInfo buildPrimaryIDForTableInfo(TableInfo info) {
|
||||
|
||||
Field idField = null;
|
||||
ID id;
|
||||
for (Field field : info.fieldToDataTypeMap.keySet()) {
|
||||
id = field.getAnnotation(ID.class);
|
||||
if (id != null) {
|
||||
idField = field;
|
||||
break;
|
||||
}
|
||||
}//end
|
||||
if (idField != null) {
|
||||
//从字段表中移除ID
|
||||
info.fieldToDataTypeMap.remove(idField);
|
||||
info.containID = true;
|
||||
info.primaryField = idField;
|
||||
} else {
|
||||
info.containID = false;
|
||||
info.primaryField = null;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名匹配TableInfo
|
||||
*
|
||||
* @param tableName
|
||||
* @return
|
||||
*/
|
||||
public static TableInfo findTableInfoByName(String tableName) {
|
||||
|
||||
for (TableInfo tableInfo : classToTableInfoMap.values()) {
|
||||
if (tableInfo.tableName.equals(tableName)) {
|
||||
return tableInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除留在内存中的TableInfo缓存
|
||||
*/
|
||||
public static void clearCache() {
|
||||
classToTableInfoMap.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
118
app/src/main/java/io/neoterm/framework/database/ValueHelper.java
Normal file
118
app/src/main/java/io/neoterm/framework/database/ValueHelper.java
Normal file
@ -0,0 +1,118 @@
|
||||
package io.neoterm.framework.database;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class ValueHelper {
|
||||
|
||||
/**
|
||||
* 根据数据类型将数据库中的值写入到相应的字段.
|
||||
*
|
||||
* @param cursor 游标
|
||||
* @param object 赋值对象
|
||||
* @param field 赋值字段
|
||||
* @param dataType 数据类型
|
||||
*/
|
||||
public static void setKeyValue(Cursor cursor, Object object, Field field, DatabaseDataType dataType, int index) {
|
||||
switch (dataType) {
|
||||
case INTEGER:
|
||||
try {
|
||||
field.set(object, cursor.getInt(index));
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
//支持Boolean类型
|
||||
//因为Boolean默认当Integer处理
|
||||
field.set(object, cursor.getInt(index) != 0);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
try {
|
||||
field.set(object, cursor.getString(index));
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
try {
|
||||
field.set(object, cursor.getFloat(index));
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
break;
|
||||
case BIGINT:
|
||||
try {
|
||||
field.set(object, cursor.getLong(index));
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
break;
|
||||
case DOUBLE:
|
||||
try {
|
||||
field.set(object, cursor.getDouble(index));
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据类型从字段中提取值并转换为String
|
||||
*
|
||||
* @param dataType
|
||||
* @param field
|
||||
* @param o
|
||||
* @return
|
||||
* @throws IllegalAccessException 无法转换时抛出的异常
|
||||
*/
|
||||
public static String valueToString(DatabaseDataType dataType, Field field, Object o) throws IllegalAccessException {
|
||||
switch (dataType) {
|
||||
case INTEGER:
|
||||
Object f = field.get(o);
|
||||
if (f instanceof Boolean) {
|
||||
return String.valueOf(((boolean) field.get(o)) ? 1 : 0);
|
||||
} else {
|
||||
return String.valueOf((int) field.get(o));
|
||||
}
|
||||
case TEXT:
|
||||
return "\"" + field.get(o) + "" + "\"";
|
||||
case DOUBLE:
|
||||
return String.valueOf((double) field.get(o));
|
||||
case FLOAT:
|
||||
return String.valueOf((float) field.get(o));
|
||||
case BIGINT:
|
||||
return String.valueOf((long) field.get(o));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据类型将对象转换为String
|
||||
*
|
||||
* @param dataType
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
public static String valueToString(DatabaseDataType dataType, Object o) {
|
||||
switch (dataType) {
|
||||
case INTEGER:
|
||||
if (o instanceof Boolean) {
|
||||
return ((boolean) o) ? "1" : "0";
|
||||
} else {
|
||||
return String.valueOf((int) o);
|
||||
}
|
||||
case TEXT:
|
||||
return "\"" + o + "\"";
|
||||
case DOUBLE:
|
||||
return String.valueOf((double) o);
|
||||
case FLOAT:
|
||||
return String.valueOf((float) o);
|
||||
case BIGINT:
|
||||
return String.valueOf((long) o);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.neoterm.framework.database.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ID {
|
||||
/**
|
||||
* 只对Integer类型的ID字段有效
|
||||
*
|
||||
* @return 是否为自增长
|
||||
*/
|
||||
boolean autoIncrement() default false;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.neoterm.framework.database.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface Ignore {
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.neoterm.framework.database.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NotNull {
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.neoterm.framework.database.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Table {
|
||||
/**
|
||||
* @return 表名
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* @return 在表创建后需要回调的方法
|
||||
*/
|
||||
String afterTableCreate() default "";
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package io.neoterm.framework.database.bean;
|
||||
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import io.neoterm.framework.database.DatabaseDataType;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class TableInfo {
|
||||
|
||||
/**
|
||||
* 是否包含ID
|
||||
*/
|
||||
public boolean containID;
|
||||
/**
|
||||
* 主键字段
|
||||
*/
|
||||
public Field primaryField;
|
||||
|
||||
/**
|
||||
* 表名
|
||||
*/
|
||||
public String tableName;
|
||||
|
||||
/**
|
||||
* 字段表
|
||||
*/
|
||||
public Map<Field, DatabaseDataType> fieldToDataTypeMap;
|
||||
|
||||
/**
|
||||
* 创建table的语句
|
||||
*/
|
||||
public String createTableStatement;
|
||||
|
||||
/**
|
||||
* 是否已经创建
|
||||
*/
|
||||
public boolean isCreate = false;
|
||||
|
||||
public Method afterTableCreateMethod;
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package io.neoterm.framework.reflection;
|
||||
|
||||
/**
|
||||
* class representing null pointer.
|
||||
* @author kiva
|
||||
*/
|
||||
public class NullPointer {
|
||||
}
|
560
app/src/main/java/io/neoterm/framework/reflection/Reflect.java
Normal file
560
app/src/main/java/io/neoterm/framework/reflection/Reflect.java
Normal file
@ -0,0 +1,560 @@
|
||||
package io.neoterm.framework.reflection;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Make reflections easier and elegant.
|
||||
*
|
||||
* @author kiva
|
||||
*/
|
||||
public class Reflect {
|
||||
private final Object mObject;
|
||||
private final boolean isClass;
|
||||
|
||||
private Reflect(Class<?> type) {
|
||||
this.mObject = type;
|
||||
this.isClass = true;
|
||||
}
|
||||
|
||||
private Reflect(Object object) {
|
||||
this.mObject = object;
|
||||
this.isClass = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create reflector from class name.
|
||||
*
|
||||
* @param name Full class name
|
||||
* @return Reflector
|
||||
* @throws ReflectionException If any error occurs
|
||||
* @see #on(Class)
|
||||
*/
|
||||
public static Reflect on(String name) throws ReflectionException {
|
||||
return on(forName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create reflector from class name using given class loader.
|
||||
*
|
||||
* @param name Full class name
|
||||
* @param classLoader Given class loader
|
||||
* @return Reflector
|
||||
* @throws ReflectionException If any error occurs
|
||||
* @see #on(Class)
|
||||
*/
|
||||
public static Reflect on(String name, ClassLoader classLoader) throws ReflectionException {
|
||||
return on(forName(name, classLoader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create reflector from given class type.
|
||||
* Helpful especially when you want to access static fields.
|
||||
*
|
||||
* @param clazz Given class type
|
||||
* @return Reflector
|
||||
*/
|
||||
public static Reflect on(Class<?> clazz) {
|
||||
return new Reflect(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an object and return its reflector.<p>
|
||||
* Helpful especially when you want to access instance fields and methods on any {@link Object}
|
||||
*
|
||||
* @param object The object to be wrapped
|
||||
* @return Reflector
|
||||
*/
|
||||
public static Reflect on(Object object) {
|
||||
return new Reflect(object);
|
||||
}
|
||||
|
||||
private static Reflect on(Method method, Object receiver, Object... args) throws ReflectionException {
|
||||
try {
|
||||
makeAccessible(method);
|
||||
|
||||
if (method.getReturnType() == void.class) {
|
||||
method.invoke(receiver, args);
|
||||
return on(receiver);
|
||||
} else {
|
||||
return on(method.invoke(receiver, args));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an {@link AccessibleObject} accessible.
|
||||
*
|
||||
* @param accessible
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T extends AccessibleObject> T makeAccessible(T accessible) {
|
||||
if (accessible == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (accessible instanceof Member) {
|
||||
Member member = (Member) accessible;
|
||||
|
||||
if (Modifier.isPublic(member.getModifiers()) &&
|
||||
Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
|
||||
|
||||
return accessible;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accessible.isAccessible()) {
|
||||
accessible.setAccessible(true);
|
||||
}
|
||||
|
||||
return accessible;
|
||||
}
|
||||
|
||||
private static String property(String string) {
|
||||
int length = string.length();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
} else if (length == 1) {
|
||||
return string.toLowerCase();
|
||||
} else {
|
||||
return string.substring(0, 1).toLowerCase() + string.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static Reflect on(Constructor<?> constructor, Object... args) throws ReflectionException {
|
||||
try {
|
||||
return on(makeAccessible(constructor).newInstance(args));
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are wrapping another reflector, get its real object.
|
||||
*/
|
||||
private static Object unwrap(Object object) {
|
||||
if (object instanceof Reflect) {
|
||||
return ((Reflect) object).get();
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object arrays into elements' class type arrays.
|
||||
* If encountered {@code null}, use {@link NullPointer}'s class type instead.
|
||||
*
|
||||
* @see Object#getClass()
|
||||
*/
|
||||
private static Class<?>[] convertTypes(Object... values) {
|
||||
if (values == null) {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
Class<?>[] result = new Class[values.length];
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
Object value = values[i];
|
||||
result[i] = value == null ? NullPointer.class : value.getClass();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class type of a class, which may cause its static-initialization
|
||||
*
|
||||
* @see Class#forName(String)
|
||||
*/
|
||||
private static Class<?> forName(String name) throws ReflectionException {
|
||||
try {
|
||||
return Class.forName(name);
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> forName(String name, ClassLoader classLoader) throws ReflectionException {
|
||||
try {
|
||||
return Class.forName(name, true, classLoader);
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap primitive class types into object class types.
|
||||
*
|
||||
* @param type Class type that may be primitive class type
|
||||
* @return Wrapped class type
|
||||
*/
|
||||
private static Class<?> wrapClassType(Class<?> type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
} else if (type.isPrimitive()) {
|
||||
if (boolean.class == type) {
|
||||
return Boolean.class;
|
||||
} else if (int.class == type) {
|
||||
return Integer.class;
|
||||
} else if (long.class == type) {
|
||||
return Long.class;
|
||||
} else if (short.class == type) {
|
||||
return Short.class;
|
||||
} else if (byte.class == type) {
|
||||
return Byte.class;
|
||||
} else if (double.class == type) {
|
||||
return Double.class;
|
||||
} else if (float.class == type) {
|
||||
return Float.class;
|
||||
} else if (char.class == type) {
|
||||
return Character.class;
|
||||
} else if (void.class == type) {
|
||||
return Void.class;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real object that reflector operates.
|
||||
*
|
||||
* @param <T> The type of the real object.
|
||||
* @return The real object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get() {
|
||||
return (T) mObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a field to given value.
|
||||
*
|
||||
* @param name Field name
|
||||
* @param value New value
|
||||
* @return Reflector
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect set(String name, Object value) throws ReflectionException {
|
||||
try {
|
||||
Field field = lookupField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(mObject, unwrap(value));
|
||||
return this;
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of given field
|
||||
*
|
||||
* @param name Field name
|
||||
* @param <T> The type of value
|
||||
* @return Value
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public <T> T get(String name) throws ReflectionException {
|
||||
return field(name).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field by name.
|
||||
*
|
||||
* @param name Field name
|
||||
* @return {@link Field}
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect field(String name) throws ReflectionException {
|
||||
try {
|
||||
Field field = lookupField(name);
|
||||
return on(field.get(mObject));
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Field lookupField(String name) throws ReflectionException {
|
||||
Class<?> type = type();
|
||||
|
||||
// 先尝试取得公有字段
|
||||
try {
|
||||
return type.getField(name);
|
||||
}
|
||||
|
||||
//此时尝试非公有字段
|
||||
catch (NoSuchFieldException e) {
|
||||
do {
|
||||
try {
|
||||
return makeAccessible(type.getDeclaredField(name));
|
||||
} catch (NoSuchFieldException ignore) {
|
||||
}
|
||||
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all fields into a map, the key is field name and the value is its reflector.
|
||||
*
|
||||
* @return Map to all fields.
|
||||
*/
|
||||
public Map<String, Reflect> fields() {
|
||||
Map<String, Reflect> result = new LinkedHashMap<String, Reflect>();
|
||||
Class<?> type = type();
|
||||
|
||||
do {
|
||||
for (Field field : type.getDeclaredFields()) {
|
||||
if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
|
||||
String name = field.getName();
|
||||
|
||||
if (!result.containsKey(name))
|
||||
result.put(name, field(name));
|
||||
}
|
||||
}
|
||||
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a method by name without parameters.
|
||||
*
|
||||
* @param name Method name
|
||||
* @return Reflector to the return value of the method
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect call(String name) throws ReflectionException {
|
||||
return call(name, new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a method by name and parameters.
|
||||
*
|
||||
* @param name Method name
|
||||
* @param args Parameters
|
||||
* @return Reflector to the return value of the method
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect call(String name, Object... args) throws ReflectionException {
|
||||
Class<?>[] types = convertTypes(args);
|
||||
|
||||
try {
|
||||
Method method = exactMethod(name, types);
|
||||
return on(method, mObject, args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
Method method = lookupSimilarMethod(name, types);
|
||||
return on(method, mObject, args);
|
||||
} catch (NoSuchMethodException e1) {
|
||||
throw new ReflectionException(e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Method exactMethod(String name, Class<?>[] types) throws NoSuchMethodException {
|
||||
Class<?> type = type();
|
||||
|
||||
try {
|
||||
return type.getMethod(name, types);
|
||||
} catch (NoSuchMethodException e) {
|
||||
do {
|
||||
try {
|
||||
return type.getDeclaredMethod(name, types);
|
||||
} catch (NoSuchMethodException ignore) {
|
||||
}
|
||||
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
throw new NoSuchMethodException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method that is similar to the wanted one.
|
||||
*/
|
||||
private Method lookupSimilarMethod(String name, Class<?>[] types) throws NoSuchMethodException {
|
||||
Class<?> type = type();
|
||||
|
||||
for (Method method : type.getMethods()) {
|
||||
if (isSignatureSimilar(method, name, types)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
if (isSignatureSimilar(method, name, types)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + ".");
|
||||
}
|
||||
|
||||
private boolean isSignatureSimilar(Method possiblyMatchingMethod,
|
||||
String wantedMethodName,
|
||||
Class<?>[] wantedParamTypes) {
|
||||
return possiblyMatchingMethod.getName().equals(wantedMethodName)
|
||||
&& match(possiblyMatchingMethod.getParameterTypes(), wantedParamTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance using its default constructor.
|
||||
*
|
||||
* @return Reflector to the return value of the method
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect create() throws ReflectionException {
|
||||
return create(new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance by parameters.
|
||||
*
|
||||
* @param args Parameters
|
||||
* @return Reflector to the return value of the method
|
||||
* @throws ReflectionException If any error occurs
|
||||
*/
|
||||
public Reflect create(Object... args) throws ReflectionException {
|
||||
Class<?>[] types = convertTypes(args);
|
||||
|
||||
|
||||
try {
|
||||
Constructor<?> constructor = type().getDeclaredConstructor(types);
|
||||
return on(constructor, args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
for (Constructor<?> constructor : type().getDeclaredConstructors()) {
|
||||
if (match(constructor.getParameterTypes(), types)) {
|
||||
return on(constructor, args);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dynamic proxy based on the given type.
|
||||
* If we are maintaining a Map and error occurs when calling methods,
|
||||
* we will return value from Map as return value.
|
||||
* Helpful especially when creating default data handlers.
|
||||
*
|
||||
* @param proxyType The type to be proxy-ed
|
||||
* @return Proxy object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P> P as(Class<P> proxyType) {
|
||||
final boolean isMap = (mObject instanceof Map);
|
||||
final InvocationHandler handler = (proxy, method, args) -> {
|
||||
String name = method.getName();
|
||||
try {
|
||||
return on(mObject).call(name, args).get();
|
||||
} catch (ReflectionException e) {
|
||||
if (isMap) {
|
||||
Map<String, Object> map = (Map<String, Object>) mObject;
|
||||
int length = (args == null ? 0 : args.length);
|
||||
|
||||
// Pay special attention to those getters and setters
|
||||
if (length == 0 && name.startsWith("get")) {
|
||||
return map.get(property(name.substring(3)));
|
||||
} else if (length == 0 && name.startsWith("is")) {
|
||||
return map.get(property(name.substring(2)));
|
||||
} else if (length == 1 && name.startsWith("set")) {
|
||||
map.put(property(name.substring(3)), args[0]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
return (P) Proxy.newProxyInstance(proxyType.getClassLoader(),
|
||||
new Class[]{proxyType}, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether types matches to avoid {@link ClassCastException} when calling a method.
|
||||
* If encountered primitive type, convert to object type first.
|
||||
*/
|
||||
private boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
|
||||
if (declaredTypes.length == actualTypes.length) {
|
||||
for (int i = 0; i < actualTypes.length; i++) {
|
||||
// nulls are acceptable on any occasions
|
||||
if (actualTypes[i] == NullPointer.class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wrapClassType(declaredTypes[i]).isAssignableFrom(wrapClassType(actualTypes[i]))) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mObject.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Reflect) {
|
||||
return mObject.equals(((Reflect) obj).get());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return mObject.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class type of the real object that reflector operates.
|
||||
*
|
||||
* @see Object#getClass()
|
||||
*/
|
||||
public Class<?> type() {
|
||||
if (isClass) {
|
||||
return (Class<?>) mObject;
|
||||
} else {
|
||||
return mObject.getClass();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.neoterm.framework.reflection;
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
public class ReflectionException extends RuntimeException {
|
||||
ReflectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ object NeoPreference {
|
||||
const val KEY_FONT_SIZE = "neoterm_general_font_size"
|
||||
const val KEY_CURRENT_SESSION = "neoterm_service_current_session"
|
||||
const val KEY_SYSTEM_SHELL = "neoterm_core_system_shell"
|
||||
const val KEY_SOURCES = "neoterm_source_source_list"
|
||||
const val KEY_SOURCES = "neoterm_package_enabled_sources"
|
||||
|
||||
const val VALUE_HAPPY_EGG_TRIGGER = 8
|
||||
|
||||
@ -56,14 +56,6 @@ object NeoPreference {
|
||||
}
|
||||
}
|
||||
|
||||
fun storeStrings(key: String, value: Set<String>) {
|
||||
preference!!.edit().putStringSet(key, value).apply()
|
||||
}
|
||||
|
||||
fun loadStrings(key: String): Set<String> {
|
||||
return preference!!.getStringSet(key, setOf())
|
||||
}
|
||||
|
||||
fun store(key: Int, value: Any) {
|
||||
store(App.get().getString(key), value)
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ object NeoTermPath {
|
||||
|
||||
private const val SOURCE = "http://janyo.pw:82/kiva/neoterm"
|
||||
|
||||
val DEFAULT_SOURCE: String
|
||||
val DEFAULT_MAIN_PACKAGE_SOURCE: String
|
||||
|
||||
init {
|
||||
DEFAULT_SOURCE = SOURCE
|
||||
DEFAULT_MAIN_PACKAGE_SOURCE = SOURCE
|
||||
}
|
||||
}
|
@ -96,10 +96,10 @@ class ColorSchemeActivity : BaseCustomizeActivity() {
|
||||
}
|
||||
|
||||
private fun showItemEditor(model: ColorItem) {
|
||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_color, null, false)
|
||||
view.findViewById<TextView>(R.id.dialog_edit_color_info).text = getString(R.string.input_new_value)
|
||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false)
|
||||
view.findViewById<TextView>(R.id.dialog_edit_text_info).text = getString(R.string.input_new_value)
|
||||
|
||||
val edit = view.findViewById<EditText>(R.id.dialog_edit_color_editor)
|
||||
val edit = view.findViewById<EditText>(R.id.dialog_edit_text_editor)
|
||||
edit.setText(model.colorValue)
|
||||
if (model.colorValue.isNotEmpty()) {
|
||||
edit.setTextColor(TerminalColors.parse(model.colorValue))
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.neoterm.ui.pm
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.support.v4.view.MenuItemCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
@ -8,16 +9,19 @@ import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.SearchView
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter
|
||||
import io.neoterm.R
|
||||
import io.neoterm.backend.TerminalSession
|
||||
import io.neoterm.component.pm.PackageComponent
|
||||
import io.neoterm.component.pm.Source
|
||||
import io.neoterm.component.pm.SourceManager
|
||||
import io.neoterm.component.pm.SourceUtils
|
||||
import io.neoterm.component.pm.SourceHelper
|
||||
import io.neoterm.frontend.component.ComponentManager
|
||||
import io.neoterm.frontend.config.NeoPreference
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
@ -109,24 +113,16 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
||||
|
||||
private fun changeSource() {
|
||||
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||
val sourceList = sourceManager.sources
|
||||
val sourceList = sourceManager.getAllSources()
|
||||
|
||||
val currentSource = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
||||
var checkedItem = sourceList.indexOf(currentSource)
|
||||
if (checkedItem == -1) {
|
||||
// Users may edit source.list on his own
|
||||
checkedItem = sourceList.size
|
||||
sourceManager.addSource(currentSource)
|
||||
}
|
||||
|
||||
var selectedIndex = 0
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.pref_package_source)
|
||||
.setSingleChoiceItems(sourceList.toTypedArray(), checkedItem, { _, which ->
|
||||
selectedIndex = which
|
||||
.setMultiChoiceItems(sourceList.map { "${it.url} :: ${it.repo}" }.toTypedArray(),
|
||||
sourceList.map { it.enabled }.toBooleanArray(), { dialog, which, isChecked ->
|
||||
sourceList[which].enabled = isChecked
|
||||
})
|
||||
.setPositiveButton(android.R.string.yes, { _, _ ->
|
||||
changeSourceInternal(sourceManager, sourceList.elementAt(selectedIndex))
|
||||
changeSourceInternal(sourceManager, sourceList)
|
||||
})
|
||||
.setNeutralButton(R.string.new_source, { _, _ ->
|
||||
changeSourceToUserInput(sourceManager)
|
||||
@ -135,26 +131,51 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
||||
.show()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun changeSourceToUserInput(sourceManager: SourceManager) {
|
||||
val editText = EditText(this)
|
||||
editText.setSelectAllOnFocus(true)
|
||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_two_text, null, false)
|
||||
view.findViewById<TextView>(R.id.dialog_edit_text_info).text = getString(R.string.input_new_source_url)
|
||||
view.findViewById<TextView>(R.id.dialog_edit_text2_info).text = getString(R.string.input_new_source_repo)
|
||||
|
||||
val urlEditor = view.findViewById<EditText>(R.id.dialog_edit_text_editor)
|
||||
val repoEditor = view.findViewById<EditText>(R.id.dialog_edit_text2_editor)
|
||||
repoEditor.setText("stable main")
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.pref_package_source)
|
||||
.setView(editText)
|
||||
.setView(view)
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setPositiveButton(android.R.string.yes, { _, _ ->
|
||||
val source = editText.text.toString()
|
||||
changeSourceInternal(sourceManager, source)
|
||||
val url = urlEditor.text.toString()
|
||||
val repo = repoEditor.text.toString()
|
||||
var errored = false
|
||||
if (url.trim().isEmpty()) {
|
||||
urlEditor.error = getString(R.string.error_new_source_url)
|
||||
errored = true
|
||||
}
|
||||
if (repo.trim().isEmpty()) {
|
||||
repoEditor.error = getString(R.string.error_new_source_repo)
|
||||
errored = true
|
||||
}
|
||||
if (errored) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
val source = urlEditor.text.toString()
|
||||
sourceManager.addSource(source, repo, true)
|
||||
postChangeSource(sourceManager)
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun changeSourceInternal(sourceManager: SourceManager, source: String) {
|
||||
sourceManager.addSource(source)
|
||||
private fun changeSourceInternal(sourceManager: SourceManager, source: List<Source>) {
|
||||
sourceManager.updateAll(source)
|
||||
postChangeSource(sourceManager)
|
||||
}
|
||||
|
||||
private fun postChangeSource(sourceManager: SourceManager) {
|
||||
sourceManager.applyChanges()
|
||||
NeoPreference.store(R.string.key_package_source, source)
|
||||
PackageUtils.syncSource()
|
||||
NeoPreference.store(R.string.key_package_source, sourceManager.getMainPackageSource())
|
||||
SourceHelper.syncSource(sourceManager)
|
||||
executeAptUpdate()
|
||||
}
|
||||
|
||||
@ -193,17 +214,11 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
||||
models.clear()
|
||||
Thread {
|
||||
val pm = ComponentManager.getComponent<PackageComponent>()
|
||||
val sourceFiles = SourceUtils.detectSourceFiles()
|
||||
val sourceFiles = SourceHelper.detectSourceFiles()
|
||||
|
||||
pm.clearPackages()
|
||||
for (index in sourceFiles.indices) {
|
||||
pm.reloadPackages(sourceFiles[index], false)
|
||||
}
|
||||
|
||||
val packages = pm.packages
|
||||
for (packageInfo in packages.values) {
|
||||
models.add(PackageModel(packageInfo))
|
||||
}
|
||||
sourceFiles.forEach { pm.reloadPackages(it, false) }
|
||||
pm.packages.values.mapTo(models, { PackageModel(it) })
|
||||
|
||||
this@PackageManagerActivity.runOnUiThread {
|
||||
adapter.edit()
|
||||
|
@ -10,6 +10,7 @@ import android.view.View
|
||||
import android.widget.*
|
||||
import io.neoterm.App
|
||||
import io.neoterm.R
|
||||
import io.neoterm.component.pm.SourceHelper
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
import io.neoterm.setup.ResultListener
|
||||
import io.neoterm.setup.SetupHelper
|
||||
@ -183,7 +184,7 @@ class SetupActivity : AppCompatActivity(), View.OnClickListener, ResultListener
|
||||
|
||||
private fun setDefaultValue(parameterEditor: EditText, id: Int) {
|
||||
setupParameter = when (id) {
|
||||
R.id.setup_method_online -> NeoTermPath.DEFAULT_SOURCE
|
||||
R.id.setup_method_online -> NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE
|
||||
else -> ""
|
||||
}
|
||||
parameterEditor.setText(setupParameter)
|
||||
@ -211,7 +212,7 @@ class SetupActivity : AppCompatActivity(), View.OnClickListener, ResultListener
|
||||
override fun onResult(error: Exception?) {
|
||||
if (error == null) {
|
||||
setResult(RESULT_OK)
|
||||
PackageUtils.syncSource()
|
||||
SourceHelper.syncSource()
|
||||
executeAptUpdate()
|
||||
|
||||
} else {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package io.neoterm.utils
|
||||
|
||||
import android.content.Context
|
||||
import io.neoterm.R
|
||||
import io.neoterm.backend.TerminalSession
|
||||
import io.neoterm.frontend.config.NeoPreference
|
||||
import io.neoterm.component.pm.PackageComponent
|
||||
import io.neoterm.component.pm.SourceManager
|
||||
import io.neoterm.frontend.component.ComponentManager
|
||||
import io.neoterm.frontend.config.NeoTermPath
|
||||
import io.neoterm.frontend.floating.TerminalDialog
|
||||
import java.io.File
|
||||
@ -12,21 +13,6 @@ import java.io.File
|
||||
* @author kiva
|
||||
*/
|
||||
object PackageUtils {
|
||||
fun syncSource() {
|
||||
val source = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
||||
val sourceFile = File(NeoTermPath.SOURCE_FILE)
|
||||
FileUtils.writeFile(sourceFile, generateSourceFile(source).toByteArray())
|
||||
}
|
||||
|
||||
private fun generateSourceFile(source: String): String {
|
||||
return StringBuilder().append("# Generated by NeoTerm-Preference\n")
|
||||
.append("deb ")
|
||||
.append(source)
|
||||
.append(" stable main")
|
||||
.append("\n")
|
||||
.toString()
|
||||
}
|
||||
|
||||
fun apt(context: Context, subCommand: String, extraArgs: Array<String>?, callback: (Int, TerminalDialog) -> Unit) {
|
||||
val argArray =
|
||||
if (extraArgs != null) arrayOf(NeoTermPath.APT_BIN_PATH, subCommand, *extraArgs)
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/text_margin">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_edit_color_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_edit_color_editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
|
||||
</LinearLayout>
|
@ -6,6 +6,7 @@
|
||||
android:padding="@dimen/text_margin">
|
||||
|
||||
<TextView
|
||||
android:labelFor="@id/dialog_edit_text_editor"
|
||||
android:id="@+id/dialog_edit_text_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
36
app/src/main/res/layout/dialog_edit_two_text.xml
Normal file
36
app/src/main/res/layout/dialog_edit_two_text.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/text_margin">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_edit_text_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/dialog_edit_text_editor"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_edit_text_editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_edit_text2_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/dialog_edit_text2_editor"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_edit_text2_editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
|
||||
</LinearLayout>
|
@ -157,6 +157,10 @@
|
||||
<string name="new_session_with_profile">新建 Profile 会话</string>
|
||||
<string name="no_profile_available">没有可用的个性化配置</string>
|
||||
<string name="no_file_picker">没有文件选择器</string>
|
||||
<string name="input_new_source_url">输入 URL</string>
|
||||
<string name="input_new_source_repo">输入仓库名</string>
|
||||
<string name="error_new_source_url">URL 不能为空</string>
|
||||
<string name="error_new_source_repo">仓库 不能为空</string>
|
||||
|
||||
<string-array name="color_item_names">
|
||||
<item>背景色</item>
|
||||
|
@ -161,6 +161,10 @@
|
||||
<string name="new_session_with_profile">New Session With Profile</string>
|
||||
<string name="no_profile_available">No profile available</string>
|
||||
<string name="no_file_picker">No file picker found</string>
|
||||
<string name="input_new_source_url">Enter new URL</string>
|
||||
<string name="input_new_source_repo">Enter new Repo</string>
|
||||
<string name="error_new_source_url">URL cannot be empty</string>
|
||||
<string name="error_new_source_repo">Repo cannot be empty</string>
|
||||
|
||||
<string name="default_source_url">http://janyo.pw:82/kiva/neoterm</string>
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package io.neoterm
|
||||
|
||||
import io.neoterm.component.pm.PackageComponent
|
||||
import io.neoterm.component.pm.SourceUtils
|
||||
import io.neoterm.component.pm.SourceHelper
|
||||
import io.neoterm.frontend.component.ComponentManager
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
@ -15,7 +14,7 @@ class PackageManagerTest {
|
||||
@Test
|
||||
fun testSourceUrl() {
|
||||
val url = "http://7sp0th.iok.la:81/neoterm"
|
||||
println(SourceUtils.detectSourceFilePrefix(url))
|
||||
println(SourceHelper.detectSourceFilePrefix(url))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user