diff --git a/data/schemas/com.m3u.data.database.M3UDatabase/19.json b/data/schemas/com.m3u.data.database.M3UDatabase/19.json new file mode 100644 index 00000000..320c381b --- /dev/null +++ b/data/schemas/com.m3u.data.database.M3UDatabase/19.json @@ -0,0 +1,353 @@ +{ + "formatVersion": 1, + "database": { + "version": 19, + "identityHash": "bb4e77821267b9b556b387f6623b6e3d", + "entities": [ + { + "tableName": "playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `url` TEXT NOT NULL, `pinned_groups` TEXT NOT NULL DEFAULT '[]', `hidden_groups` TEXT NOT NULL DEFAULT '[]', `source` TEXT NOT NULL DEFAULT '0', `user_agent` TEXT DEFAULT NULL, `epg_urls` TEXT NOT NULL DEFAULT '[]', `auto_refresh_programmes` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`url`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinnedCategories", + "columnName": "pinned_groups", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "hiddenCategories", + "columnName": "hidden_groups", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'0'" + }, + { + "fieldPath": "userAgent", + "columnName": "user_agent", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "epgUrls", + "columnName": "epg_urls", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "autoRefreshProgrammes", + "columnName": "auto_refresh_programmes", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "url" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "streams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `group` TEXT NOT NULL, `title` TEXT NOT NULL, `cover` TEXT, `playlistUrl` TEXT NOT NULL, `license_type` TEXT DEFAULT NULL, `license_key` TEXT DEFAULT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `favourite` INTEGER NOT NULL, `hidden` INTEGER NOT NULL DEFAULT 0, `seen` INTEGER NOT NULL DEFAULT 0, `channel_id` TEXT DEFAULT NULL)", + "fields": [ + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cover", + "columnName": "cover", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playlistUrl", + "columnName": "playlistUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "licenseType", + "columnName": "license_type", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "licenseKey", + "columnName": "license_key", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourite", + "columnName": "favourite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "originalId", + "columnName": "channel_id", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_streams_playlistUrl", + "unique": false, + "columnNames": [ + "playlistUrl" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_streams_playlistUrl` ON `${TABLE_NAME}` (`playlistUrl`)" + }, + { + "name": "index_streams_favourite", + "unique": false, + "columnNames": [ + "favourite" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_streams_favourite` ON `${TABLE_NAME}` (`favourite`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "programmes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channel_id` TEXT NOT NULL, `epg_url` TEXT NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `icon` TEXT, `categories` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channel_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "epgUrl", + "columnName": "epg_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_programmes_epg_url", + "unique": false, + "columnNames": [ + "epg_url" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_programmes_epg_url` ON `${TABLE_NAME}` (`epg_url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `series_id` INTEGER NOT NULL, `season` TEXT NOT NULL, `number` INTEGER NOT NULL, `url` TEXT NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "seriesId", + "columnName": "series_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "season", + "columnName": "season", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "color_pack", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`argb` INTEGER NOT NULL, `dark` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`argb`, `dark`))", + "fields": [ + { + "fieldPath": "argb", + "columnName": "argb", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDark", + "columnName": "dark", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "argb", + "dark" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bb4e77821267b9b556b387f6623b6e3d')" + ] + } +} \ No newline at end of file diff --git a/data/src/main/java/com/m3u/data/database/DatabaseMigrations.kt b/data/src/main/java/com/m3u/data/database/DatabaseMigrations.kt index 1308a8aa..ef54688d 100644 --- a/data/src/main/java/com/m3u/data/database/DatabaseMigrations.kt +++ b/data/src/main/java/com/m3u/data/database/DatabaseMigrations.kt @@ -53,4 +53,11 @@ internal object DatabaseMigrations { columnName = "epg_url" ) class AutoMigrate14To16 : AutoMigrationSpec + + @DeleteColumn.Entries( + DeleteColumn(tableName = "programmes", columnName = "new"), + DeleteColumn(tableName = "programmes", columnName = "live"), + DeleteColumn(tableName = "programmes", columnName = "previous_start") + ) + class AutoMigrate18To19: AutoMigrationSpec } \ No newline at end of file diff --git a/data/src/main/java/com/m3u/data/database/M3UDatabase.kt b/data/src/main/java/com/m3u/data/database/M3UDatabase.kt index 0956a15f..a760f73a 100644 --- a/data/src/main/java/com/m3u/data/database/M3UDatabase.kt +++ b/data/src/main/java/com/m3u/data/database/M3UDatabase.kt @@ -23,7 +23,7 @@ import com.m3u.data.database.model.Programme Episode::class, ColorScheme::class ], - version = 18, + version = 19, exportSchema = true, autoMigrations = [ AutoMigration( @@ -50,7 +50,12 @@ import com.m3u.data.database.model.Programme spec = DatabaseMigrations.AutoMigrate14To16::class ), AutoMigration(from = 16, to = 17), - AutoMigration(from = 17, to = 18) + AutoMigration(from = 17, to = 18), + AutoMigration( + from = 18, + to = 19, + spec = DatabaseMigrations.AutoMigrate18To19::class + ) ] ) @TypeConverters(Converters::class) diff --git a/data/src/main/java/com/m3u/data/database/model/Programme.kt b/data/src/main/java/com/m3u/data/database/model/Programme.kt index 846030d3..a25ea17e 100644 --- a/data/src/main/java/com/m3u/data/database/model/Programme.kt +++ b/data/src/main/java/com/m3u/data/database/model/Programme.kt @@ -29,12 +29,6 @@ data class Programme( val title: String, @ColumnInfo(name = "description") val description: String, - @ColumnInfo(name = "new", defaultValue = "0") - val isNew: Boolean, - @ColumnInfo(name = "live", defaultValue = "0") - val isLive: Boolean, - @ColumnInfo(name = "previous_start", defaultValue = "NULL") - val previouslyShownStart: String? = null, @ColumnInfo(name = "icon") val icon: String? = null, @ColumnInfo(name = "categories") diff --git a/data/src/main/java/com/m3u/data/parser/epg/EpgData.kt b/data/src/main/java/com/m3u/data/parser/epg/EpgData.kt index c33ed841..ba662e4b 100644 --- a/data/src/main/java/com/m3u/data/parser/epg/EpgData.kt +++ b/data/src/main/java/com/m3u/data/parser/epg/EpgData.kt @@ -1,6 +1,5 @@ package com.m3u.data.parser.epg -import androidx.compose.runtime.Immutable import com.m3u.data.database.model.Programme import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant @@ -8,19 +7,6 @@ import kotlinx.datetime.toKotlinLocalDateTime import java.time.LocalDateTime import java.time.format.DateTimeFormatterBuilder -@Immutable -data class EpgData( - val channels: List = emptyList(), - val programmes: List = emptyList() -) - -data class EpgChannel( - val id: String, - val displayName: String? = null, - val icon: String? = null, - val url: String? = null -) - data class EpgProgramme( val channel: String, // use [readEpochMilliseconds] @@ -30,9 +16,6 @@ data class EpgProgramme( val title: String? = null, val desc: String? = null, val icon: String? = null, - val isNew: Boolean = false, - val isLive: Boolean = false, - val previouslyShownStart: String? = null, val categories: List ) { companion object { @@ -65,9 +48,6 @@ fun EpgProgramme.toProgramme( title = title.orEmpty(), description = desc.orEmpty(), icon = icon, - isNew = isNew, - isLive = isLive, - previouslyShownStart = previouslyShownStart, categories = categories, channelId = channel ) diff --git a/data/src/main/java/com/m3u/data/parser/epg/EpgParserImpl.kt b/data/src/main/java/com/m3u/data/parser/epg/EpgParserImpl.kt index 4a3c8147..17f308f7 100644 --- a/data/src/main/java/com/m3u/data/parser/epg/EpgParserImpl.kt +++ b/data/src/main/java/com/m3u/data/parser/epg/EpgParserImpl.kt @@ -42,49 +42,6 @@ class EpgParserImpl @Inject constructor( .flowOn(ioDispatcher) private val ns: String? = null - private fun XmlPullParser.readEpg(): EpgData { - while (name != "tv") next() - val channels = mutableListOf() - val programmes = mutableListOf() - while (next() != XmlPullParser.END_TAG) { - if (eventType != XmlPullParser.START_TAG) continue - when (name) { - "channel" -> channels += readChannel() - "programme" -> programmes += readProgramme() - else -> skip() - } - } - logger.log("complete: channel+${channels.size}, programme+${programmes.size}") - return EpgData( - channels = channels, - programmes = programmes - ) - } - - private fun XmlPullParser.readChannel(): EpgChannel { - require(XmlPullParser.START_TAG, ns, "channel") - val id = getAttributeValue(null, "id") - var displayName: String? = null - var icon: String? = null - var url: String? = null - while (next() != XmlPullParser.END_TAG) { - if (eventType != XmlPullParser.START_TAG) continue - when (name) { - "display-name" -> displayName = readDisplayName() - "icon" -> icon = readIcon() - "url" -> url = readUrl() - else -> skip() - } - } - require(XmlPullParser.END_TAG, ns, "channel") - return EpgChannel( - id = id, - displayName = displayName, - icon = icon, - url = url - ) - } - private fun XmlPullParser.readProgramme(): EpgProgramme { require(XmlPullParser.START_TAG, ns, "programme") val start = getAttributeValue(null, "start") @@ -94,9 +51,6 @@ class EpgParserImpl @Inject constructor( var desc: String? = null val categories = mutableListOf() var icon: String? = null - var isNew = false // Initialize isNew flag - var isLive = false - var previouslyShownStart: String? = null // Initialize previouslyShown variable while (next() != XmlPullParser.END_TAG) { if (eventType != XmlPullParser.START_TAG) continue when (name) { @@ -104,9 +58,6 @@ class EpgParserImpl @Inject constructor( "desc" -> desc = readDesc() "category" -> categories += readCategory() "icon" -> icon = readIcon() - "new" -> isNew = readNew() // Update isNewTag flag - "live" -> isLive = readLive() // Update isNewTag flag - "previously-shown" -> previouslyShownStart = readPreviouslyShown() else -> skip() } } @@ -118,10 +69,7 @@ class EpgParserImpl @Inject constructor( title = title, desc = desc, icon = icon, - categories = categories, - isNew = isNew, - isLive = isLive, - previouslyShownStart = previouslyShownStart + categories = categories ) } @@ -136,13 +84,6 @@ class EpgParserImpl @Inject constructor( } } - private fun XmlPullParser.readDisplayName(): String? = optional { - require(XmlPullParser.START_TAG, ns, "display-name") - val displayName = readText() - require(XmlPullParser.END_TAG, ns, "display-name") - return displayName - } - private fun XmlPullParser.readIcon(): String? = optional { require(XmlPullParser.START_TAG, ns, "icon") val icon = getAttributeValue(null, "src") @@ -151,13 +92,6 @@ class EpgParserImpl @Inject constructor( return icon } - private fun XmlPullParser.readUrl(): String? = optional { - require(XmlPullParser.START_TAG, ns, "url") - val url = readText() - require(XmlPullParser.END_TAG, ns, "url") - return url - } - private fun XmlPullParser.readTitle(): String? = optional { require(XmlPullParser.START_TAG, ns, "title") val title = readText() @@ -179,24 +113,6 @@ class EpgParserImpl @Inject constructor( return category } - private fun XmlPullParser.readNew(): Boolean { - require(XmlPullParser.END_TAG, ns, "new") - return true - } - - private fun XmlPullParser.readLive(): Boolean { - require(XmlPullParser.END_TAG, ns, "live") - return true - } - - private fun XmlPullParser.readPreviouslyShown(): String? { - require(XmlPullParser.START_TAG, ns, "previously-shown") - val start = getAttributeValue(null, "start") - nextTag() - require(XmlPullParser.END_TAG, ns, "previously-shown") - return start - } - private fun XmlPullParser.readText(): String { var result = "" if (next() == XmlPullParser.TEXT) {