2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter.table
|
|
|
|
|
|
|
|
import android.content.ContentValues
|
|
|
|
import android.database.sqlite.SQLiteDatabase
|
|
|
|
import android.provider.BaseColumns
|
2023-02-04 21:52:26 +01:00
|
|
|
import jp.juggler.subwaytooter.api.entity.Acct
|
2018-08-30 08:51:32 +02:00
|
|
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
|
|
|
import jp.juggler.subwaytooter.api.entity.putMayNull
|
2023-02-05 16:44:28 +01:00
|
|
|
import jp.juggler.util.data.*
|
2023-01-13 13:22:25 +01:00
|
|
|
import jp.juggler.util.log.LogCategory
|
2020-12-11 18:30:10 +01:00
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
class NotificationTracking {
|
2020-12-11 18:30:10 +01:00
|
|
|
|
2021-06-20 15:12:25 +02:00
|
|
|
private var dirty = false
|
|
|
|
|
|
|
|
private var id = -1L
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// アカウントDBの行ID。 サーバ側のIDではない
|
2021-06-20 15:12:25 +02:00
|
|
|
private var accountDbId: Long = 0
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// 返信だけ通知グループを分ける
|
2021-06-20 15:12:25 +02:00
|
|
|
private var notificationType: String = ""
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// 通知ID。ここまで既読
|
2021-06-20 15:12:25 +02:00
|
|
|
var nid_read: EntityId? = null
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// 通知ID。もっとも最近取得したもの
|
2021-06-20 15:12:25 +02:00
|
|
|
var nid_show: EntityId? = null
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// 最後に表示した通知のID
|
2021-06-20 15:12:25 +02:00
|
|
|
var post_id: EntityId? = null
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
// 最後に表示した通知の作成時刻
|
2021-06-20 15:12:25 +02:00
|
|
|
var post_time: Long = 0
|
|
|
|
set(value) {
|
|
|
|
dirty = true; field = value
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object : TableCompanion {
|
|
|
|
private val log = LogCategory("NotificationTracking")
|
2021-10-28 01:37:39 +02:00
|
|
|
override val table = "noti_trac"
|
2021-06-20 15:12:25 +02:00
|
|
|
private const val COL_ID = BaseColumns._ID
|
|
|
|
private const val COL_ACCOUNT_DB_ID = "a"
|
|
|
|
private const val COL_NID_READ = "nr"
|
|
|
|
private const val COL_NID_SHOW = "ns"
|
|
|
|
private const val COL_POST_ID = "pi"
|
|
|
|
private const val COL_POST_TIME = "pt"
|
|
|
|
private const val COL_NOTIFICATION_TYPE = "nt"
|
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
private val metaColumns = MetaColumns(table, initialVersion = 0).apply {
|
|
|
|
column(0, COL_ID, "INTEGER PRIMARY KEY")
|
|
|
|
column(0, COL_ACCOUNT_DB_ID, "integer not null")
|
|
|
|
column(0, COL_NID_READ, "text")
|
|
|
|
column(0, COL_NID_SHOW, "text")
|
|
|
|
column(0, COL_POST_ID, "text")
|
|
|
|
column(0, COL_POST_TIME, "integer default 0")
|
|
|
|
column(40, COL_NOTIFICATION_TYPE, "text default ''")
|
|
|
|
deleteBeforeCreate = true
|
|
|
|
createExtra = {
|
|
|
|
arrayOf(
|
|
|
|
"create unique index if not exists ${table}_b on $table($COL_ACCOUNT_DB_ID,$COL_NOTIFICATION_TYPE)"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 15:12:25 +02:00
|
|
|
|
2023-02-05 16:44:28 +01:00
|
|
|
override fun onDBCreate(db: SQLiteDatabase) {
|
|
|
|
metaColumns.onDBCreate(db)
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
|
|
|
|
|
|
if (newVersion < oldVersion) {
|
|
|
|
try {
|
|
|
|
db.execSQL("drop table if exists $table")
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
log.e(ex, "delete DB failed.")
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
2023-02-05 16:44:28 +01:00
|
|
|
metaColumns.onDBCreate(db)
|
2021-06-20 15:12:25 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldVersion < 2 && newVersion >= 2) {
|
2023-02-05 16:44:28 +01:00
|
|
|
metaColumns.onDBCreate(db)
|
2021-06-20 15:12:25 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldVersion < 40 && newVersion >= 40) {
|
|
|
|
try {
|
|
|
|
db.execSQL("drop index if exists ${table}_a")
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
log.e(ex, "drop index failed.")
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
2023-02-05 16:44:28 +01:00
|
|
|
}
|
|
|
|
metaColumns.onDBUpgrade(db, oldVersion, newVersion)
|
|
|
|
if (oldVersion < 40 && newVersion >= 40) {
|
2021-06-20 15:12:25 +02:00
|
|
|
try {
|
|
|
|
db.execSQL(
|
|
|
|
"create unique index if not exists ${table}_b on $table ($COL_ACCOUNT_DB_ID,$COL_NOTIFICATION_TYPE)"
|
|
|
|
)
|
|
|
|
} catch (ex: Throwable) {
|
2022-12-27 03:54:52 +01:00
|
|
|
log.e(ex, "can't add index.")
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-28 01:37:39 +02:00
|
|
|
private val cache =
|
|
|
|
ConcurrentHashMap<Long, ConcurrentHashMap<String, NotificationTracking>>()
|
2021-06-20 15:12:25 +02:00
|
|
|
|
2021-10-28 01:37:39 +02:00
|
|
|
private fun <K : Any, V : Any> ConcurrentHashMap<K, V>.getOrCreate(
|
|
|
|
key: K,
|
2022-12-27 03:54:52 +01:00
|
|
|
creator: () -> V,
|
2021-10-28 01:37:39 +02:00
|
|
|
): V {
|
2021-06-20 15:12:25 +02:00
|
|
|
var v = this[key]
|
|
|
|
if (v == null) v = creator().also { this[key] = it }
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun loadCache(accountDbId: Long, notificationType: String): NotificationTracking? =
|
|
|
|
cache[accountDbId]?.get(notificationType)
|
|
|
|
|
|
|
|
private fun clearCache(accountDbId: Long, notificationType: String): NotificationTracking? =
|
|
|
|
cache[accountDbId]?.remove(notificationType)
|
|
|
|
|
2021-10-28 01:37:39 +02:00
|
|
|
private fun saveCache(
|
|
|
|
accountDbId: Long,
|
|
|
|
notificationType: String,
|
2022-12-27 03:54:52 +01:00
|
|
|
nt: NotificationTracking,
|
2021-10-28 01:37:39 +02:00
|
|
|
) {
|
2021-06-20 15:12:25 +02:00
|
|
|
cache.getOrCreate(accountDbId) {
|
|
|
|
ConcurrentHashMap<String, NotificationTracking>()
|
|
|
|
}[notificationType] = nt
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
private const val WHERE_AID = "$COL_ACCOUNT_DB_ID=? and $COL_NOTIFICATION_TYPE=?"
|
2023-02-04 21:52:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fun toContentValues() = ContentValues().apply {
|
|
|
|
put(COL_ACCOUNT_DB_ID, accountDbId)
|
|
|
|
put(COL_NOTIFICATION_TYPE, notificationType)
|
|
|
|
nid_read.putMayNull(this, COL_NID_READ)
|
|
|
|
nid_show.putMayNull(this, COL_NID_SHOW)
|
|
|
|
post_id.putMayNull(this, COL_POST_ID)
|
|
|
|
put(COL_POST_TIME, post_time)
|
|
|
|
}
|
|
|
|
|
|
|
|
class Access(val db: SQLiteDatabase) {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
2023-02-04 21:52:26 +01:00
|
|
|
fun load(
|
|
|
|
// ログ出力だけに使う
|
|
|
|
acct: Acct,
|
|
|
|
accountDbId: Long,
|
|
|
|
notificationType: String,
|
|
|
|
): NotificationTracking {
|
|
|
|
loadCache(accountDbId, notificationType)
|
|
|
|
?.takeIf { !it.dirty }
|
|
|
|
?.let {
|
2021-06-20 15:12:25 +02:00
|
|
|
log.d(
|
2023-02-04 21:52:26 +01:00
|
|
|
"${acct.pretty}/$notificationType load-cached. post=(${it.post_id},${it.post_time}), read=${it.nid_read}, show=${it.nid_show}"
|
2021-06-20 15:12:25 +02:00
|
|
|
)
|
2023-02-04 21:52:26 +01:00
|
|
|
return it
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
|
|
|
|
2023-02-04 21:52:26 +01:00
|
|
|
val whereArgs = arrayOf(accountDbId.toString(), notificationType)
|
2021-06-20 15:12:25 +02:00
|
|
|
val dst = NotificationTracking()
|
|
|
|
dst.accountDbId = accountDbId
|
|
|
|
dst.notificationType = notificationType
|
|
|
|
try {
|
2023-02-04 21:52:26 +01:00
|
|
|
db.rawQuery(
|
|
|
|
"select * from $table where $COL_ACCOUNT_DB_ID=? and $COL_NOTIFICATION_TYPE=?",
|
|
|
|
whereArgs,
|
2021-06-20 15:12:25 +02:00
|
|
|
)?.use { cursor ->
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
dst.id = cursor.getLong(COL_ID)
|
|
|
|
|
2023-02-07 13:49:45 +01:00
|
|
|
dst.post_id = EntityId.entityId(cursor, COL_POST_ID)
|
2021-06-20 15:12:25 +02:00
|
|
|
dst.post_time = cursor.getLong(COL_POST_TIME)
|
|
|
|
|
2023-02-07 13:49:45 +01:00
|
|
|
val show = EntityId.entityId(cursor, COL_NID_SHOW)
|
2021-06-20 15:12:25 +02:00
|
|
|
if (show == null) {
|
|
|
|
dst.nid_show = null
|
|
|
|
dst.nid_read = null
|
|
|
|
} else {
|
|
|
|
dst.nid_show = show
|
2023-02-07 13:49:45 +01:00
|
|
|
val read = EntityId.entityId(cursor, COL_NID_READ)
|
2021-06-20 15:12:25 +02:00
|
|
|
if (read == null) {
|
|
|
|
dst.nid_read = null
|
|
|
|
} else {
|
|
|
|
val r2 = minComparable(show, read)
|
|
|
|
dst.nid_read = r2
|
|
|
|
if (r2 != read) {
|
|
|
|
log.i("$acct/$notificationType read>show! clip to $show")
|
|
|
|
val cv = ContentValues()
|
|
|
|
show.putTo(cv, COL_NID_READ) //変数名とキー名が異なるのに注意
|
2023-02-04 21:52:26 +01:00
|
|
|
db.update(table, cv, WHERE_AID, whereArgs)
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.d(
|
2023-02-04 21:52:26 +01:00
|
|
|
"${acct.pretty}/$notificationType load. post=(${dst.post_id},${dst.post_time}), read=${dst.nid_read}, show=${dst.nid_show}"
|
2021-06-20 15:12:25 +02:00
|
|
|
)
|
|
|
|
saveCache(accountDbId, notificationType, dst)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
2023-02-04 21:52:26 +01:00
|
|
|
log.e(ex, "${acct.pretty} load failed.")
|
2021-06-20 15:12:25 +02:00
|
|
|
} finally {
|
|
|
|
dst.dirty = false
|
|
|
|
}
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
|
|
|
fun updateRead(accountDbId: Long, notificationType: String) {
|
|
|
|
try {
|
2023-02-04 21:52:26 +01:00
|
|
|
val whereArgs = arrayOf(accountDbId.toString(), notificationType)
|
|
|
|
db.rawQuery(
|
|
|
|
"select $COL_NID_SHOW, $COL_NID_READ from $table where $COL_ACCOUNT_DB_ID=? and $COL_NOTIFICATION_TYPE=?",
|
|
|
|
whereArgs,
|
2021-06-20 15:12:25 +02:00
|
|
|
)?.use { cursor ->
|
|
|
|
when {
|
|
|
|
!cursor.moveToFirst() -> log.e("updateRead[$accountDbId,$notificationType]: can't find the data row.")
|
|
|
|
|
|
|
|
else -> {
|
2023-02-07 13:49:45 +01:00
|
|
|
val nid_show = EntityId.entityId(cursor, COL_NID_SHOW)
|
|
|
|
val nid_read = EntityId.entityId(cursor, COL_NID_READ)
|
2021-06-20 15:12:25 +02:00
|
|
|
when {
|
|
|
|
nid_show == null ->
|
|
|
|
log.e("updateRead[$accountDbId,$notificationType]: nid_show is null.")
|
|
|
|
|
|
|
|
nid_read != null && nid_read >= nid_show ->
|
|
|
|
log.e("updateRead[$accountDbId,$notificationType]: nid_read already updated.")
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
log.i("updateRead[$accountDbId,$notificationType]: update nid_read as $nid_show...")
|
|
|
|
val cv = ContentValues()
|
|
|
|
nid_show.putTo(cv, COL_NID_READ) //変数名とキー名が異なるのに注意
|
2023-02-04 21:52:26 +01:00
|
|
|
db.update(table, cv, WHERE_AID, whereArgs)
|
2021-06-20 15:12:25 +02:00
|
|
|
clearCache(accountDbId, notificationType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "updateRead[$accountDbId] failed.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun resetPostAll() {
|
|
|
|
try {
|
|
|
|
val cv = ContentValues()
|
|
|
|
cv.putNull(COL_POST_ID)
|
|
|
|
cv.put(COL_POST_TIME, 0)
|
2023-02-04 21:52:26 +01:00
|
|
|
db.update(table, cv, null, null)
|
2021-06-20 15:12:25 +02:00
|
|
|
cache.clear()
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "resetPostAll failed.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// アカウント設定から手動で呼ばれる
|
|
|
|
fun resetTrackingState(accountDbId: Long?) {
|
|
|
|
accountDbId ?: return
|
|
|
|
try {
|
2023-02-04 21:52:26 +01:00
|
|
|
db.delete(table, "$COL_ACCOUNT_DB_ID=?", arrayOf(accountDbId.toString()))
|
2021-06-20 15:12:25 +02:00
|
|
|
cache.remove(accountDbId)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "resetTrackingState failed.")
|
|
|
|
}
|
|
|
|
}
|
2023-02-04 21:52:26 +01:00
|
|
|
|
|
|
|
fun save(
|
|
|
|
// ログ出力だけに使う
|
|
|
|
acct: Acct,
|
|
|
|
item: NotificationTracking,
|
|
|
|
) {
|
|
|
|
try {
|
|
|
|
if (!item.dirty) return
|
|
|
|
val rowId = item.toContentValues()
|
|
|
|
.replaceTo(db, table)
|
|
|
|
if (item.id == -1L) item.id = rowId
|
|
|
|
|
|
|
|
log.d("${acct.pretty}/${item.notificationType} save. post=(${item.post_id},${item.post_time})")
|
|
|
|
item.dirty = false
|
|
|
|
clearCache(item.accountDbId, item.notificationType)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "save failed.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun updatePost(postId: EntityId, postTime: Long, item: NotificationTracking) {
|
|
|
|
try {
|
|
|
|
item.post_id = postId
|
|
|
|
item.post_time = postTime
|
|
|
|
if (!item.dirty) return
|
|
|
|
val cv = item.toContentValues().apply {
|
|
|
|
postId.putTo(this, COL_POST_ID)
|
|
|
|
put(COL_POST_TIME, postTime)
|
|
|
|
}
|
|
|
|
val rows = db.update(
|
|
|
|
table,
|
|
|
|
cv,
|
|
|
|
WHERE_AID,
|
|
|
|
arrayOf(item.accountDbId.toString(), item.notificationType)
|
|
|
|
)
|
|
|
|
log.d("updatePost account_db_id=${item.accountDbId}, nt=${item.notificationType}, post=${postId},${postTime} update_rows=${rows}")
|
|
|
|
item.dirty = false
|
|
|
|
clearCache(item.accountDbId, item.notificationType)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "updatePost failed.")
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 15:12:25 +02:00
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|