package com.sptmobile.voice import android.content.Context import androidx.room.ColumnInfo import androidx.room.Dao import androidx.room.Database import androidx.room.Entity import androidx.room.Insert import androidx.room.PrimaryKey import androidx.room.Query import androidx.room.Room import androidx.room.RoomDatabase import java.util.UUID import kotlinx.coroutines.flow.Flow @Entity(tableName = "voice_spool") data class VoiceSpoolEntity( @PrimaryKey(autoGenerate = true) val seq: Long = 0, @ColumnInfo(name = "msg_id") val msgId: String, val transcription: String, @ColumnInfo(name = "recorded_at_ms") val recordedAtMs: Long, @ColumnInfo(name = "spooled_at_ms") val spooledAtMs: Long, ) @Dao interface VoiceSpoolDao { @Insert fun insert(row: VoiceSpoolEntity): Long @Query("SELECT * FROM voice_spool ORDER BY seq LIMIT 1") fun oldest(): VoiceSpoolEntity? @Query("DELETE FROM voice_spool WHERE seq = :seq") fun remove(seq: Long) @Query("SELECT COUNT(*) FROM voice_spool") fun count(): Flow } @Database(entities = [VoiceSpoolEntity::class], version = 1, exportSchema = false) abstract class VoiceDb : RoomDatabase() { abstract fun spool(): VoiceSpoolDao } // [impl->REQ-VOICE-PIPE] /** * Room-backed [VoiceSpool] (ruling 6: the phone spool is Room/SQLite; * APP-PLAN Q4 deferred Room to exactly this plan). [enqueue] returns only * after the insert transaction commits — that commit is the durability line * the webhook receiver's 200 stands behind (REQ-HAZARD-DICTATION-LOSS: kill * the process after the 200 and the row is still there). */ class RoomVoiceSpool private constructor(private val dao: VoiceSpoolDao) : VoiceSpool { constructor(context: Context, dbName: String = "voice_spool.db") : this( Room.databaseBuilder(context.applicationContext, VoiceDb::class.java, dbName) .build() .spool() ) // [impl->REQ-HAZARD-DICTATION-LOSS] override fun enqueue(transcription: String, recordedAtMs: Long): VoiceRow { val entity = VoiceSpoolEntity( msgId = UUID.randomUUID().toString(), transcription = transcription, recordedAtMs = recordedAtMs, spooledAtMs = System.currentTimeMillis(), ) val seq = dao.insert(entity) return VoiceRow(seq, entity.msgId, transcription, recordedAtMs, entity.spooledAtMs) } override fun oldest(): VoiceRow? = dao.oldest()?.let { VoiceRow(it.seq, it.msgId, it.transcription, it.recordedAtMs, it.spooledAtMs) } override fun remove(seq: Long) = dao.remove(seq) override val pending: Flow get() = dao.count() }