155 lines
5.2 KiB
Kotlin
155 lines
5.2 KiB
Kotlin
package jp.juggler.subwaytooter.table
|
|
|
|
import android.content.ContentValues
|
|
import android.content.Context
|
|
import android.database.sqlite.SQLiteDatabase
|
|
import android.database.sqlite.SQLiteDatabaseCorruptException
|
|
import android.database.sqlite.SQLiteOpenHelper
|
|
import android.provider.BaseColumns
|
|
import jp.juggler.util.data.TableCompanion
|
|
import jp.juggler.util.data.getBlobOrNull
|
|
import jp.juggler.util.data.getLong
|
|
import jp.juggler.util.log.LogCategory
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
private val log = LogCategory("EmojiCacheDatabase")
|
|
|
|
// カスタム絵文字のキャッシュ専用のデータベースファイルを作る
|
|
// (DB破損などの際に削除してしまえるようにする)
|
|
private const val CACHE_DB_NAME = "emoji_cache_db"
|
|
private const val CACHE_DB_VERSION = 1
|
|
|
|
class EmojiCache(
|
|
val id: Long,
|
|
val timeUsed: Long,
|
|
val data: ByteArray,
|
|
) {
|
|
companion object : TableCompanion {
|
|
|
|
override val table = "custom_emoji_cache"
|
|
|
|
const val COL_ID = BaseColumns._ID
|
|
const val COL_TIME_SAVE = "time_save"
|
|
const val COL_TIME_USED = "time_used"
|
|
const val COL_URL = "url"
|
|
const val COL_DATA = "data"
|
|
|
|
override fun onDBCreate(db: SQLiteDatabase) {
|
|
db.execSQL(
|
|
"""create table if not exists $table
|
|
($COL_ID INTEGER PRIMARY KEY
|
|
,$COL_TIME_SAVE integer not null
|
|
,$COL_TIME_USED integer not null
|
|
,$COL_URL text not null
|
|
,$COL_DATA blob not null
|
|
)""".trimIndent()
|
|
)
|
|
db.execSQL("create unique index if not exists ${table}_url on $table($COL_URL)")
|
|
db.execSQL("create index if not exists ${table}_old on $table($COL_TIME_USED)")
|
|
}
|
|
|
|
override fun onDBUpgrade(
|
|
db: SQLiteDatabase,
|
|
oldVersion: Int,
|
|
newVersion: Int,
|
|
) {
|
|
}
|
|
}
|
|
|
|
class Access(val db: SQLiteDatabase) {
|
|
fun load(url: String, now: Long) =
|
|
db.rawQuery(
|
|
"select $COL_ID,$COL_TIME_USED,$COL_DATA from $table where $COL_URL=?",
|
|
arrayOf(url)
|
|
)?.use { cursor ->
|
|
if (cursor.moveToNext()) {
|
|
EmojiCache(
|
|
id = cursor.getLong(COL_ID),
|
|
timeUsed = cursor.getLong(COL_TIME_USED),
|
|
data = cursor.getBlobOrNull(COL_DATA)!!
|
|
).apply {
|
|
if (now - timeUsed >= 5 * 3600000L) {
|
|
db.update(
|
|
table,
|
|
ContentValues().apply {
|
|
put(COL_TIME_USED, now)
|
|
},
|
|
"$COL_ID=?",
|
|
arrayOf(id.toString())
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
null
|
|
}
|
|
}
|
|
|
|
fun sweep(now: Long) {
|
|
val expire = now - TimeUnit.DAYS.toMillis(30)
|
|
db.delete(
|
|
table,
|
|
"$COL_TIME_USED < ?",
|
|
arrayOf(expire.toString())
|
|
)
|
|
}
|
|
|
|
fun update(url: String, data: ByteArray) {
|
|
val now = System.currentTimeMillis()
|
|
db.replace(table,
|
|
null,
|
|
ContentValues().apply {
|
|
put(COL_URL, url)
|
|
put(COL_DATA, data)
|
|
put(COL_TIME_USED, now)
|
|
put(COL_TIME_SAVE, now)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
class EmojiCacheDbOpenHelper(val context: Context) :
|
|
SQLiteOpenHelper(context, CACHE_DB_NAME, null, CACHE_DB_VERSION) {
|
|
|
|
private val tables = arrayOf(EmojiCache)
|
|
override fun onCreate(db: SQLiteDatabase) =
|
|
tables.forEach { it.onDBCreate(db) }
|
|
|
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
|
|
tables.forEach { it.onDBUpgrade(db, oldVersion, newVersion) }
|
|
|
|
fun deleteDatabase() {
|
|
try {
|
|
close()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "deleteDatabase: close() failed.")
|
|
}
|
|
try {
|
|
SQLiteDatabase.deleteDatabase(context.getDatabasePath(databaseName))
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "deleteDatabase failed.")
|
|
}
|
|
}
|
|
|
|
// DB処理を行い、SQLiteDatabaseCorruptExceptionを検出したらDBを削除してリトライする
|
|
fun <T : Any> access(block: EmojiCache.Access.() -> T?): T? {
|
|
for (nTry in 0 until 3) {
|
|
try {
|
|
val db = writableDatabase
|
|
if (db == null) {
|
|
log.e("access[$nTry]: writableDatabase returns null.")
|
|
break
|
|
}
|
|
return EmojiCache.Access(db).block()
|
|
} catch (ex: SQLiteDatabaseCorruptException) {
|
|
log.e(ex, "access[$nTry]: db corrupt!")
|
|
deleteDatabase()
|
|
} catch (ex: Throwable) {
|
|
log.e(ex, "access[$nTry]: failed.")
|
|
break
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
}
|