package jp.juggler.subwaytooter.table import android.content.ContentValues import android.database.sqlite.SQLiteDatabase import android.provider.BaseColumns import jp.juggler.subwaytooter.api.entity.Acct import jp.juggler.subwaytooter.api.entity.EntityId import jp.juggler.util.data.MetaColumns import jp.juggler.util.data.TableCompanion import jp.juggler.util.data.replaceTo import jp.juggler.util.log.LogCategory import kotlin.math.min class NotificationShown( var id: Long = 0L, var acct: String = "", var notificationId: String = "", var timeCreate: Long = System.currentTimeMillis(), ) { companion object : TableCompanion { private val log = LogCategory("NotificationShown") override val table = "notification_shown" private const val COL_ID = BaseColumns._ID private const val COL_ACCT = "a" private const val COL_NOTIFICATION_ID = "ni" private const val COL_TIME_CREATE = "tc" private val columnList = MetaColumns(table, initialVersion = 65).apply { column(0, COL_ID, MetaColumns.TS_INT_PRIMARY_KEY_NOT_NULL) column(0, COL_ACCT, MetaColumns.TS_EMPTY_NOT_NULL) column(0, COL_NOTIFICATION_ID, MetaColumns.TS_EMPTY_NOT_NULL) column(0, COL_TIME_CREATE, MetaColumns.TS_ZERO_NOT_NULL) createExtra = { arrayOf( "create unique index if not exists ${table}_a on $table($COL_ACCT,$COL_NOTIFICATION_ID)", "create index if not exists ${table}_at on $table($COL_ACCT,$COL_TIME_CREATE)", ) } } override fun onDBCreate(db: SQLiteDatabase) { columnList.onDBCreate(db) } override fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { if (oldVersion < 65 && newVersion >= 65) { onDBCreate(db) } } } class Access(val db: SQLiteDatabase) { fun deleteOld() { try { val list = db.rawQuery( "select $COL_ACCT,count(*) from $table GROUP BY $COL_ACCT", emptyArray(), )?.use { cursor -> buildList { while (cursor.moveToNext()) { add( Pair( cursor.getString(0), cursor.getInt(1), ) ) } } } list ?: error("can't get usage count group by $COL_ACCT") // ある程度の量は残したい val keep = 200 for (pair in list) { val acct = pair.first var size = pair.second log.i("$acct size=$size") // 掃除する頻度を下げるため、ここのしきい値は倍にする if (size <= keep * 2) continue // アカウントごとに3回の削除を一度に行い、残りは次回以降にすませる var deleteCount = 0 while (size > keep && deleteCount++ < 3) { // 一度に削除したい数 val step = min(1000, size - keep) if (step <= 0) break // 古いものからstep件目の時刻を読む val time = db.rawQuery( "select $COL_TIME_CREATE from $table where $COL_ACCT=? order by $COL_TIME_CREATE asc limit ?", arrayOf(acct, step.toString()) )?.use { cursor -> when (cursor.moveToLast()) { true -> cursor.getLong(0) else -> null } } if (time == null || time <= 0L) { log.e("can't get time of position $step") break } // 時刻がそれ以下のデータを削除する db.execSQL( "delete from $table where $COL_ACCT=? and $COL_TIME_CREATE<=?", arrayOf(acct, time.toString()) ) // 件数を読み直す size = db.rawQuery( "select count(*) from $table where $COL_ACCT=?", arrayOf(acct), )?.use { cursor -> when (cursor.moveToNext()) { true -> cursor.getInt(0) else -> null } } ?: -1 log.i("$acct size=$size") if (size < 0) { log.e("can't get size for $acct") break } } } } catch (ex: Throwable) { log.e(ex, "deleteOld failed.") } } fun cleayByAcct(acct: Acct) { db.execSQL( "delete from $table where $COL_ACCT=?", arrayOf(acct) ) } fun duplicateOrPut(acct: Acct, notificationId: String): Boolean { try { // 有効なIDがない場合は重複排除しない when (notificationId) { "", EntityId.DEFAULT.toString() -> return false } db.rawQuery( "select $COL_ID from $table where $COL_ACCT=? and $COL_NOTIFICATION_ID=? limit 1", arrayOf(acct.ascii, notificationId) )?.use { if (it.count > 0) return true } ContentValues().apply { put(COL_TIME_CREATE, System.currentTimeMillis()) put(COL_ACCT, acct.ascii) put(COL_NOTIFICATION_ID, notificationId) }.replaceTo(db, table) } catch (ex: Throwable) { log.e(ex, "duplicateOrPut failed.") } return false } fun isDuplicate(acct: Acct, notificationId: String): Boolean { try { // 有効なIDがない場合は重複排除しない when (notificationId) { "", EntityId.DEFAULT.toString() -> return false } db.rawQuery( "select $COL_ID from $table where $COL_ACCT=? and $COL_NOTIFICATION_ID=? limit 1", arrayOf(acct.ascii, notificationId) )?.use { if (it.count > 0) return true } } catch (ex: Throwable) { log.e(ex, "isDuplicate failed.") } return false } } }