You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

144 lines
4.3 KiB

package com.timesafari.dailynotification
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
/**
* SQLite schema for Daily Notification Plugin
* Implements TTL-at-fire invariant and rolling window armed design
*
* @author Matthew Raymer
* @version 1.1.0
*/
@Entity(tableName = "content_cache")
data class ContentCache(
@PrimaryKey val id: String,
val fetchedAt: Long, // epoch ms
val ttlSeconds: Int,
val payload: ByteArray, // BLOB
val meta: String? = null
)
@Entity(tableName = "schedules")
data class Schedule(
@PrimaryKey val id: String,
val kind: String, // 'fetch' or 'notify'
val cron: String? = null, // optional cron expression
val clockTime: String? = null, // optional HH:mm
val enabled: Boolean = true,
val lastRunAt: Long? = null,
val nextRunAt: Long? = null,
val jitterMs: Int = 0,
val backoffPolicy: String = "exp",
val stateJson: String? = null
)
@Entity(tableName = "callbacks")
data class Callback(
@PrimaryKey val id: String,
val kind: String, // 'http', 'local', 'queue'
val target: String, // url_or_local
val headersJson: String? = null,
val enabled: Boolean = true,
val createdAt: Long
)
@Entity(tableName = "history")
data class History(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val refId: String, // content or schedule id
val kind: String, // fetch/notify/callback
val occurredAt: Long,
val durationMs: Long? = null,
val outcome: String, // success|failure|skipped_ttl|circuit_open
val diagJson: String? = null
)
@Database(
entities = [ContentCache::class, Schedule::class, Callback::class, History::class],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class DailyNotificationDatabase : RoomDatabase() {
abstract fun contentCacheDao(): ContentCacheDao
abstract fun scheduleDao(): ScheduleDao
abstract fun callbackDao(): CallbackDao
abstract fun historyDao(): HistoryDao
}
@Dao
interface ContentCacheDao {
@Query("SELECT * FROM content_cache WHERE id = :id")
suspend fun getById(id: String): ContentCache?
@Query("SELECT * FROM content_cache ORDER BY fetchedAt DESC LIMIT 1")
suspend fun getLatest(): ContentCache?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(contentCache: ContentCache)
@Query("DELETE FROM content_cache WHERE fetchedAt < :cutoffTime")
suspend fun deleteOlderThan(cutoffTime: Long)
@Query("SELECT COUNT(*) FROM content_cache")
suspend fun getCount(): Int
}
@Dao
interface ScheduleDao {
@Query("SELECT * FROM schedules WHERE enabled = 1")
suspend fun getEnabled(): List<Schedule>
@Query("SELECT * FROM schedules WHERE id = :id")
suspend fun getById(id: String): Schedule?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(schedule: Schedule)
@Query("UPDATE schedules SET enabled = :enabled WHERE id = :id")
suspend fun setEnabled(id: String, enabled: Boolean)
@Query("UPDATE schedules SET lastRunAt = :lastRunAt, nextRunAt = :nextRunAt WHERE id = :id")
suspend fun updateRunTimes(id: String, lastRunAt: Long?, nextRunAt: Long?)
}
@Dao
interface CallbackDao {
@Query("SELECT * FROM callbacks WHERE enabled = 1")
suspend fun getEnabled(): List<Callback>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(callback: Callback)
@Query("DELETE FROM callbacks WHERE id = :id")
suspend fun deleteById(id: String)
}
@Dao
interface HistoryDao {
@Insert
suspend fun insert(history: History)
@Query("SELECT * FROM history WHERE occurredAt >= :since ORDER BY occurredAt DESC")
suspend fun getSince(since: Long): List<History>
@Query("DELETE FROM history WHERE occurredAt < :cutoffTime")
suspend fun deleteOlderThan(cutoffTime: Long)
@Query("SELECT COUNT(*) FROM history")
suspend fun getCount(): Int
}
class Converters {
@TypeConverter
fun fromByteArray(value: ByteArray?): String? {
return value?.let { String(it) }
}
@TypeConverter
fun toByteArray(value: String?): ByteArray? {
return value?.toByteArray()
}
}