在从中恢复之前,如何验证 sqlite 文件是否属于我的 android 应用程序
How can I verify that sqlite file belongs to my android app before restoring from it
我很久以前就在我的应用程序中实现了简单的 backup/restore 解决方案。原理很简单,创建备份时将内部数据库复制到外部存储,从备份恢复时 - 将外部数据库文件复制到内部数据库。
但是当我尝试从另一个应用程序(显然不包含我的应用程序需要的 tables)的备份文件恢复时,问题就来了。我如何验证 somefile.db
或 somefile.sqlite
实际上是我的应用程序的备份?它确实包含所需的架构。因为如果我从随机 file.db 恢复,我的应用程序会崩溃并显示数据库不包含所需 table
的消息
fun restoreDbFromUri(uri: Uri): Boolean {
try {
val cr = context.contentResolver
cr.openFileDescriptor(uri, "r")
?.use { pfd ->
FileInputStream(pfd.fileDescriptor)
.use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables
dbOpenHelper.close()
internalDbFile.transferFrom(fis)
return true
}
}
} catch (e: Exception) {
presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
e.printStackTrace()
}
presenter.toastPresenter.onShowErrorToast()
return false
}
val internalDbFile: File
get() = context.getDatabasePath("prana_breath.sqlite")
@Throws(IOException::class)
fun File.transferFrom(srcInput: FileInputStream) {
FileOutputStream(this).use { dstOut ->
dstOut.channel.use { dstChannel ->
srcInput.channel.use { srcChannel ->
dstChannel.transferFrom(srcChannel)
}
}
}
}
@Throws(IOException::class)
fun FileChannel.transferFrom(srcChannel: FileChannel) {
this.transferFrom(srcChannel, 0, srcChannel.size())
}
您可以先打开文件,检查文件头的前16个字节必须是"SQLite format 3[=36=]0".
例如:-
const val SQLITEFILEHEADER = "SQLite format 3\u0000"
private fun isFileSQLiteDatabase(f: File): Boolean {
val fis: InputStream
if (!f.isFile) return false
val header = ByteArray(16)
try {
fis = FileInputStream(f)
fis.read(header)
if (String(header) != SQLITEFILEHEADER) {
fis.close()
return false
}
} catch (e: IOException) {
return false
}
return true
}
这将消除几乎所有非数据库文件。然后要检查数据库是否具有预期的 tables(and/or 其他实体),您可以使用 SQliteDatabase 的 openDatabase 方法之一将文件作为 SQLiteDatabase 打开,然后查询 sqlite_master table 检查预期的 tables(and/or 其他实体)是否按预期存在,确保您关闭数据库。
所以你可以有一些类似的东西:-
private fun isDBValid(f: File, entityList: Array<String>): Boolean {
var matchcount = 0
if (!isFileSQLiteDatabase(f)) return false
try {
val db = SQLiteDatabase.openDatabase(f.path, null, SQLiteDatabase.OPEN_READWRITE)
val csr = db.query("sqlite_master", null, null, null, null, null, null)
while (csr.moveToNext()) {
for (s in entityList) {
if (s.toLowerCase() == csr.getString(csr.getColumnIndex("name")).toLowerCase()) {
matchcount++
break
}
}
}
csr.close()
db.close()
} catch (e: SQLiteException) {
return false;
}
return matchcount != entityList.size
}
- 传递的字符串数组将是必须找到的 tables(或其他实体、索引、视图、触发器等)的列表。
然后您可以使用 :-
fun restore DbFromUri(uri: Uri): Boolean {
//<<<<< DB VERIFICATION >>>>>
if (isDBValid(File(uri.path), requiredEntities)) {
} else {
return false
}
try {
val cr = context.contentResolver
cr.openFileDescriptor(uri, "r")
?.use { pfd ->
FileInputStream(pfd.fileDescriptor)
.use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables !!!!DONE ABOVE!!!!
dbOpenHelper.close()
internalDbFile.transferFrom(fis)
return true
}
}
} catch (e: Exception) {
presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
e.printStackTrace()
}
presenter.toastPresenter.onShowErrorToast()
return false
}
- 请注意以上是原理代码,尚未经过测试或运行因此可能包含一些错误。
我很久以前就在我的应用程序中实现了简单的 backup/restore 解决方案。原理很简单,创建备份时将内部数据库复制到外部存储,从备份恢复时 - 将外部数据库文件复制到内部数据库。
但是当我尝试从另一个应用程序(显然不包含我的应用程序需要的 tables)的备份文件恢复时,问题就来了。我如何验证 somefile.db
或 somefile.sqlite
实际上是我的应用程序的备份?它确实包含所需的架构。因为如果我从随机 file.db 恢复,我的应用程序会崩溃并显示数据库不包含所需 table
fun restoreDbFromUri(uri: Uri): Boolean {
try {
val cr = context.contentResolver
cr.openFileDescriptor(uri, "r")
?.use { pfd ->
FileInputStream(pfd.fileDescriptor)
.use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables
dbOpenHelper.close()
internalDbFile.transferFrom(fis)
return true
}
}
} catch (e: Exception) {
presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
e.printStackTrace()
}
presenter.toastPresenter.onShowErrorToast()
return false
}
val internalDbFile: File
get() = context.getDatabasePath("prana_breath.sqlite")
@Throws(IOException::class)
fun File.transferFrom(srcInput: FileInputStream) {
FileOutputStream(this).use { dstOut ->
dstOut.channel.use { dstChannel ->
srcInput.channel.use { srcChannel ->
dstChannel.transferFrom(srcChannel)
}
}
}
}
@Throws(IOException::class)
fun FileChannel.transferFrom(srcChannel: FileChannel) {
this.transferFrom(srcChannel, 0, srcChannel.size())
}
您可以先打开文件,检查文件头的前16个字节必须是"SQLite format 3[=36=]0".
例如:-
const val SQLITEFILEHEADER = "SQLite format 3\u0000"
private fun isFileSQLiteDatabase(f: File): Boolean {
val fis: InputStream
if (!f.isFile) return false
val header = ByteArray(16)
try {
fis = FileInputStream(f)
fis.read(header)
if (String(header) != SQLITEFILEHEADER) {
fis.close()
return false
}
} catch (e: IOException) {
return false
}
return true
}
这将消除几乎所有非数据库文件。然后要检查数据库是否具有预期的 tables(and/or 其他实体),您可以使用 SQliteDatabase 的 openDatabase 方法之一将文件作为 SQLiteDatabase 打开,然后查询 sqlite_master table 检查预期的 tables(and/or 其他实体)是否按预期存在,确保您关闭数据库。
所以你可以有一些类似的东西:-
private fun isDBValid(f: File, entityList: Array<String>): Boolean {
var matchcount = 0
if (!isFileSQLiteDatabase(f)) return false
try {
val db = SQLiteDatabase.openDatabase(f.path, null, SQLiteDatabase.OPEN_READWRITE)
val csr = db.query("sqlite_master", null, null, null, null, null, null)
while (csr.moveToNext()) {
for (s in entityList) {
if (s.toLowerCase() == csr.getString(csr.getColumnIndex("name")).toLowerCase()) {
matchcount++
break
}
}
}
csr.close()
db.close()
} catch (e: SQLiteException) {
return false;
}
return matchcount != entityList.size
}
- 传递的字符串数组将是必须找到的 tables(或其他实体、索引、视图、触发器等)的列表。
然后您可以使用 :-
fun restore DbFromUri(uri: Uri): Boolean {
//<<<<< DB VERIFICATION >>>>>
if (isDBValid(File(uri.path), requiredEntities)) {
} else {
return false
}
try {
val cr = context.contentResolver
cr.openFileDescriptor(uri, "r")
?.use { pfd ->
FileInputStream(pfd.fileDescriptor)
.use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables !!!!DONE ABOVE!!!!
dbOpenHelper.close()
internalDbFile.transferFrom(fis)
return true
}
}
} catch (e: Exception) {
presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
e.printStackTrace()
}
presenter.toastPresenter.onShowErrorToast()
return false
}
- 请注意以上是原理代码,尚未经过测试或运行因此可能包含一些错误。