mirror of https://github.com/readrops/Readrops.git
Remove Item.guid field, use Item.remoteId instead for all account types
This commit is contained in:
parent
44b2858cb0
commit
a00ef31cf7
|
@ -4,7 +4,7 @@ import com.gitlab.mvysny.konsumexml.Konsumer
|
||||||
import com.gitlab.mvysny.konsumexml.Names
|
import com.gitlab.mvysny.konsumexml.Names
|
||||||
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
|
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
|
||||||
import com.readrops.api.localfeed.XmlAdapter
|
import com.readrops.api.localfeed.XmlAdapter
|
||||||
import com.readrops.api.utils.*
|
import com.readrops.api.utils.DateUtils
|
||||||
import com.readrops.api.utils.exceptions.ParseException
|
import com.readrops.api.utils.exceptions.ParseException
|
||||||
import com.readrops.api.utils.extensions.nonNullText
|
import com.readrops.api.utils.extensions.nonNullText
|
||||||
import com.readrops.api.utils.extensions.nullableText
|
import com.readrops.api.utils.extensions.nullableText
|
||||||
|
@ -22,7 +22,7 @@ class ATOMItemAdapter : XmlAdapter<Item> {
|
||||||
konsumer.allChildrenAutoIgnore(names) {
|
konsumer.allChildrenAutoIgnore(names) {
|
||||||
when (tagName) {
|
when (tagName) {
|
||||||
"title" -> title = nonNullText()
|
"title" -> title = nonNullText()
|
||||||
"id" -> guid = nullableText()
|
"id" -> remoteId = nullableText()
|
||||||
"updated" -> pubDate = DateUtils.parse(nullableText())
|
"updated" -> pubDate = DateUtils.parse(nullableText())
|
||||||
"link" -> parseLink(this, this@apply)
|
"link" -> parseLink(this, this@apply)
|
||||||
"author" -> allChildrenAutoIgnore("name") { author = nullableText() }
|
"author" -> allChildrenAutoIgnore("name") { author = nullableText() }
|
||||||
|
@ -35,7 +35,7 @@ class ATOMItemAdapter : XmlAdapter<Item> {
|
||||||
|
|
||||||
validateItem(item)
|
validateItem(item)
|
||||||
if (item.pubDate == null) item.pubDate = LocalDateTime.now()
|
if (item.pubDate == null) item.pubDate = LocalDateTime.now()
|
||||||
if (item.guid == null) item.guid = item.link
|
if (item.remoteId == null) item.remoteId = item.link
|
||||||
|
|
||||||
item
|
item
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ class JSONItemsAdapter : JsonAdapter<List<Item>>() {
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
with(item) {
|
with(item) {
|
||||||
when (selectName(names)) {
|
when (selectName(names)) {
|
||||||
0 -> guid = nextNonEmptyString()
|
0 -> remoteId = nextNonEmptyString()
|
||||||
1 -> link = nextNonEmptyString()
|
1 -> link = nextNonEmptyString()
|
||||||
2 -> title = nextNonEmptyString()
|
2 -> title = nextNonEmptyString()
|
||||||
3 -> contentHtml = nextNullableString()
|
3 -> contentHtml = nextNullableString()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import com.gitlab.mvysny.konsumexml.Names
|
||||||
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
|
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
|
||||||
import com.readrops.api.localfeed.XmlAdapter
|
import com.readrops.api.localfeed.XmlAdapter
|
||||||
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
|
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
|
||||||
import com.readrops.api.utils.*
|
import com.readrops.api.utils.DateUtils
|
||||||
import com.readrops.api.utils.exceptions.ParseException
|
import com.readrops.api.utils.exceptions.ParseException
|
||||||
import com.readrops.api.utils.extensions.nonNullText
|
import com.readrops.api.utils.extensions.nonNullText
|
||||||
import com.readrops.api.utils.extensions.nullableText
|
import com.readrops.api.utils.extensions.nullableText
|
||||||
|
@ -40,7 +40,7 @@ class RSS1ItemAdapter : XmlAdapter<Item> {
|
||||||
if (item.pubDate == null) item.pubDate = LocalDateTime.now()
|
if (item.pubDate == null) item.pubDate = LocalDateTime.now()
|
||||||
if (item.link == null) item.link = about
|
if (item.link == null) item.link = about
|
||||||
?: throw ParseException("RSS1 link or about element is required")
|
?: throw ParseException("RSS1 link or about element is required")
|
||||||
item.guid = item.link
|
item.remoteId = item.link
|
||||||
|
|
||||||
if (authors.filterNotNull().isNotEmpty()) item.author = authors.filterNotNull()
|
if (authors.filterNotNull().isNotEmpty()) item.author = authors.filterNotNull()
|
||||||
.joinToString(limit = AUTHORS_MAX)
|
.joinToString(limit = AUTHORS_MAX)
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package com.readrops.api.localfeed.rss2
|
package com.readrops.api.localfeed.rss2
|
||||||
|
|
||||||
import com.gitlab.mvysny.konsumexml.*
|
import com.gitlab.mvysny.konsumexml.Konsumer
|
||||||
|
import com.gitlab.mvysny.konsumexml.KonsumerException
|
||||||
|
import com.gitlab.mvysny.konsumexml.Names
|
||||||
|
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
|
||||||
import com.readrops.api.localfeed.XmlAdapter
|
import com.readrops.api.localfeed.XmlAdapter
|
||||||
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
|
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
|
||||||
import com.readrops.api.utils.*
|
import com.readrops.api.utils.ApiUtils
|
||||||
|
import com.readrops.api.utils.DateUtils
|
||||||
import com.readrops.api.utils.exceptions.ParseException
|
import com.readrops.api.utils.exceptions.ParseException
|
||||||
import com.readrops.api.utils.extensions.nonNullText
|
import com.readrops.api.utils.extensions.nonNullText
|
||||||
import com.readrops.api.utils.extensions.nullableText
|
import com.readrops.api.utils.extensions.nullableText
|
||||||
|
@ -29,7 +33,7 @@ class RSS2ItemAdapter : XmlAdapter<Item> {
|
||||||
"dc:creator" -> creators += nullableText()
|
"dc:creator" -> creators += nullableText()
|
||||||
"pubDate" -> pubDate = DateUtils.parse(nullableText())
|
"pubDate" -> pubDate = DateUtils.parse(nullableText())
|
||||||
"dc:date" -> pubDate = DateUtils.parse(nullableText())
|
"dc:date" -> pubDate = DateUtils.parse(nullableText())
|
||||||
"guid" -> guid = nullableText()
|
"guid" -> remoteId = nullableText()
|
||||||
"description" -> description = nullableTextRecursively()
|
"description" -> description = nullableTextRecursively()
|
||||||
"content:encoded" -> content = nullableTextRecursively()
|
"content:encoded" -> content = nullableTextRecursively()
|
||||||
"enclosure" -> parseEnclosure(this, item = this@apply)
|
"enclosure" -> parseEnclosure(this, item = this@apply)
|
||||||
|
@ -81,7 +85,7 @@ class RSS2ItemAdapter : XmlAdapter<Item> {
|
||||||
validateItem(this)
|
validateItem(this)
|
||||||
|
|
||||||
if (pubDate == null) pubDate = LocalDateTime.now()
|
if (pubDate == null) pubDate = LocalDateTime.now()
|
||||||
if (guid == null) guid = link
|
if (remoteId == null) remoteId = link
|
||||||
if (author == null && creators.filterNotNull().isNotEmpty())
|
if (author == null && creators.filterNotNull().isNotEmpty())
|
||||||
author = creators.filterNotNull().joinToString(limit = AUTHORS_MAX)
|
author = creators.filterNotNull().joinToString(limit = AUTHORS_MAX)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package com.readrops.api.services.nextcloudnews.adapters
|
package com.readrops.api.services.nextcloudnews.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import com.readrops.db.entities.Item
|
|
||||||
import com.readrops.api.utils.ApiUtils
|
import com.readrops.api.utils.ApiUtils
|
||||||
import com.readrops.api.utils.exceptions.ParseException
|
import com.readrops.api.utils.exceptions.ParseException
|
||||||
import com.readrops.api.utils.extensions.nextNonEmptyString
|
import com.readrops.api.utils.extensions.nextNonEmptyString
|
||||||
import com.readrops.api.utils.extensions.nextNullableString
|
import com.readrops.api.utils.extensions.nextNullableString
|
||||||
|
import com.readrops.db.entities.Item
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.JsonReader
|
import com.squareup.moshi.JsonReader
|
||||||
import com.squareup.moshi.JsonWriter
|
import com.squareup.moshi.JsonWriter
|
||||||
|
@ -49,7 +49,6 @@ class NextcloudNewsItemsAdapter : JsonAdapter<List<Item>>() {
|
||||||
8 -> feedRemoteId = reader.nextInt().toString()
|
8 -> feedRemoteId = reader.nextInt().toString()
|
||||||
9 -> isRead = !reader.nextBoolean() // the negation is important here
|
9 -> isRead = !reader.nextBoolean() // the negation is important here
|
||||||
10 -> isStarred = reader.nextBoolean()
|
10 -> isStarred = reader.nextBoolean()
|
||||||
11 -> guid = reader.nextNullableString()
|
|
||||||
else -> reader.skipValue()
|
else -> reader.skipValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +72,6 @@ class NextcloudNewsItemsAdapter : JsonAdapter<List<Item>>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val NAMES: JsonReader.Options = JsonReader.Options.of("id", "url", "title", "author",
|
val NAMES: JsonReader.Options = JsonReader.Options.of("id", "url", "title", "author",
|
||||||
"pubDate", "body", "enclosureMime", "enclosureLink", "feedId", "unread", "starred", "guidHash")
|
"pubDate", "body", "enclosureMime", "enclosureLink", "feedId", "unread", "starred")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,6 @@ import junit.framework.TestCase.assertEquals
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
class ATOMAdapterTest {
|
class ATOMAdapterTest {
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ class ATOMAdapterTest {
|
||||||
assertEquals(pubDate, DateUtils.parse("2020-09-06T21:09:59Z"))
|
assertEquals(pubDate, DateUtils.parse("2020-09-06T21:09:59Z"))
|
||||||
assertEquals(author, "Shinokuni")
|
assertEquals(author, "Shinokuni")
|
||||||
assertEquals(description, "Summary")
|
assertEquals(description, "Summary")
|
||||||
assertEquals(guid, "tag:github.com,2008:Grit::Commit/c15f093a1bc4211e85f8d1817c9073e307afe5ac")
|
assertEquals(remoteId, "tag:github.com,2008:Grit::Commit/c15f093a1bc4211e85f8d1817c9073e307afe5ac")
|
||||||
TestCase.assertNotNull(content)
|
TestCase.assertNotNull(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import junit.framework.TestCase.assertEquals
|
import junit.framework.TestCase.assertEquals
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -40,7 +39,7 @@ class JSONFeedAdapterTest {
|
||||||
|
|
||||||
with(items[0]) {
|
with(items[0]) {
|
||||||
assertEquals(items.size, 10)
|
assertEquals(items.size, 10)
|
||||||
assertEquals(guid, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html")
|
assertEquals(remoteId, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html")
|
||||||
assertEquals(title, "Acorn and 10.13")
|
assertEquals(title, "Acorn and 10.13")
|
||||||
assertEquals(link, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html")
|
assertEquals(link, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html")
|
||||||
assertEquals(pubDate, DateUtils.parse("2017-09-25T14:27:27-07:00"))
|
assertEquals(pubDate, DateUtils.parse("2017-09-25T14:27:27-07:00"))
|
||||||
|
|
|
@ -35,7 +35,7 @@ class RSS1AdapterTest {
|
||||||
assertEquals(title, "Google Expands its Flutter Development Kit To Windows Apps")
|
assertEquals(title, "Google Expands its Flutter Development Kit To Windows Apps")
|
||||||
assertEquals(link!!.trim(), "https://developers.slashdot.org/story/20/09/23/1616231/google-expands-" +
|
assertEquals(link!!.trim(), "https://developers.slashdot.org/story/20/09/23/1616231/google-expands-" +
|
||||||
"its-flutter-development-kit-to-windows-apps?utm_source=rss1.0mainlinkanon&utm_medium=feed")
|
"its-flutter-development-kit-to-windows-apps?utm_source=rss1.0mainlinkanon&utm_medium=feed")
|
||||||
assertEquals(guid!!.trim(), "https://developers.slashdot.org/story/20/09/23/1616231/google-expands-" +
|
assertEquals(remoteId!!.trim(), "https://developers.slashdot.org/story/20/09/23/1616231/google-expands-" +
|
||||||
"its-flutter-development-kit-to-windows-apps?utm_source=rss1.0mainlinkanon&utm_medium=feed")
|
"its-flutter-development-kit-to-windows-apps?utm_source=rss1.0mainlinkanon&utm_medium=feed")
|
||||||
assertEquals(pubDate, DateUtils.parse("2020-09-23T16:15:00+00:00"))
|
assertEquals(pubDate, DateUtils.parse("2020-09-23T16:15:00+00:00"))
|
||||||
assertEquals(author, "msmash")
|
assertEquals(author, "msmash")
|
||||||
|
|
|
@ -36,7 +36,7 @@ class RSS2AdapterTest {
|
||||||
assertEquals(pubDate, DateUtils.parse("Tue, 25 Aug 2020 17:15:49 +0000"))
|
assertEquals(pubDate, DateUtils.parse("Tue, 25 Aug 2020 17:15:49 +0000"))
|
||||||
assertEquals(author, "Author 1")
|
assertEquals(author, "Author 1")
|
||||||
assertEquals(description, "<a href=\"https://news.ycombinator.com/item?id=24273602\">Comments</a>")
|
assertEquals(description, "<a href=\"https://news.ycombinator.com/item?id=24273602\">Comments</a>")
|
||||||
assertEquals(guid, "https://www.bbc.com/news/world-africa-53887947")
|
assertEquals(remoteId, "https://www.bbc.com/news/world-africa-53887947")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class RSS2AdapterTest {
|
||||||
val stream = TestUtils.loadResource("localfeed/rss2/rss_items_other_namespaces.xml")
|
val stream = TestUtils.loadResource("localfeed/rss2/rss_items_other_namespaces.xml")
|
||||||
val item = adapter.fromXml(stream.konsumeXml()).second[0]
|
val item = adapter.fromXml(stream.konsumeXml()).second[0]
|
||||||
|
|
||||||
assertEquals(item.guid, "guid")
|
assertEquals(item.remoteId, "guid")
|
||||||
assertEquals(item.author, "creator 1, creator 2, creator 3, creator 4")
|
assertEquals(item.author, "creator 1, creator 2, creator 3, creator 4")
|
||||||
assertEquals(item.pubDate, DateUtils.parse("2020-08-05T14:03:48Z"))
|
assertEquals(item.pubDate, DateUtils.parse("2020-08-05T14:03:48Z"))
|
||||||
assertEquals(item.content, "content:encoded")
|
assertEquals(item.content, "content:encoded")
|
||||||
|
|
|
@ -25,7 +25,6 @@ class NextcloudNewsItemsAdapterTest {
|
||||||
|
|
||||||
with(item) {
|
with(item) {
|
||||||
assertEquals(remoteId, "3443")
|
assertEquals(remoteId, "3443")
|
||||||
assertEquals(guid, "3059047a572cd9cd5d0bf645faffd077")
|
|
||||||
assertEquals(link, "http://grulja.wordpress.com/2013/04/29/plasma-nm-after-the-solid-sprint/")
|
assertEquals(link, "http://grulja.wordpress.com/2013/04/29/plasma-nm-after-the-solid-sprint/")
|
||||||
assertEquals(title, "Plasma-nm after the solid sprint")
|
assertEquals(title, "Plasma-nm after the solid sprint")
|
||||||
assertEquals(author, "Jan Grulich (grulja)")
|
assertEquals(author, "Jan Grulich (grulja)")
|
||||||
|
|
|
@ -90,7 +90,7 @@ class LocalRSSRepositoryTest : KoinTest {
|
||||||
|
|
||||||
assertTrue { result.first.items.isNotEmpty() }
|
assertTrue { result.first.items.isNotEmpty() }
|
||||||
assertTrue {
|
assertTrue {
|
||||||
database.itemDao().itemExists(result.first.items.first().guid!!, account.id)
|
database.itemDao().itemExists(result.first.items.first().remoteId!!, account.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class LocalRSSRepositoryTest : KoinTest {
|
||||||
|
|
||||||
assertTrue { result.first.items.isNotEmpty() }
|
assertTrue { result.first.items.isNotEmpty() }
|
||||||
assertTrue {
|
assertTrue {
|
||||||
database.itemDao().itemExists(result.first.items.first().guid!!, account.id)
|
database.itemDao().itemExists(result.first.items.first().remoteId!!, account.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -87,7 +87,7 @@ class LocalRSSRepository(
|
||||||
val newItems = mutableListOf<Item>()
|
val newItems = mutableListOf<Item>()
|
||||||
|
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
if (!database.itemDao().itemExists(item.guid!!, feed.accountId)) {
|
if (!database.itemDao().itemExists(item.remoteId!!, feed.accountId)) {
|
||||||
if (item.description != null) {
|
if (item.description != null) {
|
||||||
item.cleanDescription = Jsoup.parse(item.description).text()
|
item.cleanDescription = Jsoup.parse(item.description).text()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"identityHash": "5043c0aff2ed15f8cbe250e35bb9129e",
|
"identityHash": "7059fa306ee4013c51c8a521e04e3e31",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "Feed",
|
"tableName": "Feed",
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "Item",
|
"tableName": "Item",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `guid` TEXT, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `read_it_later` INTEGER NOT NULL, `remoteId` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `read_it_later` INTEGER NOT NULL, `remoteId` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
@ -215,12 +215,6 @@
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldPath": "guid",
|
|
||||||
"columnName": "guid",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldPath": "readTime",
|
"fieldPath": "readTime",
|
||||||
"columnName": "read_time",
|
"columnName": "read_time",
|
||||||
|
@ -267,15 +261,6 @@
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Item_feed_id` ON `${TABLE_NAME}` (`feed_id`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Item_feed_id` ON `${TABLE_NAME}` (`feed_id`)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "index_Item_guid",
|
|
||||||
"unique": false,
|
|
||||||
"columnNames": [
|
|
||||||
"guid"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Item_guid` ON `${TABLE_NAME}` (`guid`)"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
|
@ -547,7 +532,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '5043c0aff2ed15f8cbe250e35bb9129e')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7059fa306ee4013c51c8a521e04e3e31')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package com.readrops.db
|
||||||
import androidx.room.testing.MigrationTestHelper
|
import androidx.room.testing.MigrationTestHelper
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import junit.framework.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -39,4 +40,18 @@ class MigrationsTest {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrate3to4() {
|
||||||
|
helper.createDatabase(dbName, 3).apply {
|
||||||
|
execSQL("Insert Into Account(account_type, last_modified, current_account, notifications_enabled) Values(0, 0, 0, 0)")
|
||||||
|
execSQL("Insert Into Feed(text_color, background_color, account_id, notification_enabled) Values(0, 0, 3, 0)")
|
||||||
|
execSQL("Insert Into Item(title, feed_id, read_time, read, starred, read_it_later, guid) values(\"test\", 12, 0, 0, 0, 0, \"guid\")")
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.runMigrationsAndValidate(dbName, 4, true, MigrationFrom3To4).apply {
|
||||||
|
val remoteId = compileStatement("Select remoteId From Item").simpleQueryForString()
|
||||||
|
assertEquals("guid", remoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package com.readrops.db
|
package com.readrops.db
|
||||||
|
|
||||||
import androidx.room.AutoMigration
|
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
|
@ -21,10 +20,8 @@ import com.readrops.db.entities.account.Account
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Feed::class, Item::class, Folder::class, Account::class,
|
entities = [Feed::class, Item::class, Folder::class, Account::class,
|
||||||
ItemStateChange::class, ItemState::class], version = 4,
|
ItemStateChange::class, ItemState::class],
|
||||||
autoMigrations = [
|
version = 4
|
||||||
AutoMigration(3, 4)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class Database : RoomDatabase() {
|
abstract class Database : RoomDatabase() {
|
||||||
|
@ -57,7 +54,7 @@ object MigrationFrom2To3 : Migration(2, 3) {
|
||||||
db.execSQL("""CREATE TABLE IF NOT EXISTS `ItemStateChange` (`id` INTEGER NOT NULL, `read_change` INTEGER NOT NULL, `star_change` INTEGER NOT NULL, `account_id` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
db.execSQL("""CREATE TABLE IF NOT EXISTS `ItemStateChange` (`id` INTEGER NOT NULL, `read_change` INTEGER NOT NULL, `star_change` INTEGER NOT NULL, `account_id` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
||||||
db.execSQL("""CREATE TABLE IF NOT EXISTS `ItemState` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `remote_id` TEXT NOT NULL, `account_id` INTEGER NOT NULL, FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
db.execSQL("""CREATE TABLE IF NOT EXISTS `ItemState` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `remote_id` TEXT NOT NULL, `account_id` INTEGER NOT NULL, FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
||||||
|
|
||||||
// removing read_changed and adding starred fields. Table is recreated to keep field order
|
// removing read_changed and adding starred fields
|
||||||
db.execSQL("""CREATE TABLE IF NOT EXISTS `Item_MERGE_TABLE` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `guid` TEXT, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `read_it_later` INTEGER NOT NULL, `remoteId` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
db.execSQL("""CREATE TABLE IF NOT EXISTS `Item_MERGE_TABLE` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `guid` TEXT, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `read_it_later` INTEGER NOT NULL, `remoteId` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )""")
|
||||||
db.execSQL("""INSERT INTO `Item_MERGE_TABLE` (`id`,`title`,`description`,`clean_description`,`link`,`image_link`,`author`,`pub_date`,`content`,`feed_id`,`guid`,`read_time`,`read`,`read_it_later`,`remoteId`,`starred`) SELECT `Item`.`id`,`Item`.`title`,`Item`.`description`,`Item`.`clean_description`,`Item`.`link`,`Item`.`image_link`,`Item`.`author`,`Item`.`pub_date`,`Item`.`content`,`Item`.`feed_id`,`Item`.`guid`,`Item`.`read_time`,`Item`.`read`,`Item`.`read_it_later`,`Item`.`remoteId`,0 FROM `Item`""")
|
db.execSQL("""INSERT INTO `Item_MERGE_TABLE` (`id`,`title`,`description`,`clean_description`,`link`,`image_link`,`author`,`pub_date`,`content`,`feed_id`,`guid`,`read_time`,`read`,`read_it_later`,`remoteId`,`starred`) SELECT `Item`.`id`,`Item`.`title`,`Item`.`description`,`Item`.`clean_description`,`Item`.`link`,`Item`.`image_link`,`Item`.`author`,`Item`.`pub_date`,`Item`.`content`,`Item`.`feed_id`,`Item`.`guid`,`Item`.`read_time`,`Item`.`read`,`Item`.`read_it_later`,`Item`.`remoteId`,0 FROM `Item`""")
|
||||||
db.execSQL("""DROP TABLE IF EXISTS `Item`""")
|
db.execSQL("""DROP TABLE IF EXISTS `Item`""")
|
||||||
|
@ -66,3 +63,24 @@ object MigrationFrom2To3 : Migration(2, 3) {
|
||||||
db.execSQL("""CREATE INDEX IF NOT EXISTS `index_Item_guid` ON `Item` (`guid`)""")
|
db.execSQL("""CREATE INDEX IF NOT EXISTS `index_Item_guid` ON `Item` (`guid`)""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object MigrationFrom3To4 : Migration(3, 4) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
// add unique index to ItemState.(account_id, remote_id)
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS `_new_ItemState` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `remote_id` TEXT NOT NULL, `account_id` INTEGER NOT NULL, FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||||
|
db.execSQL("INSERT INTO `_new_ItemState` (`id`,`read`,`starred`,`remote_id`,`account_id`) SELECT `id`,`read`,`starred`,`remote_id`,`account_id` FROM `ItemState`")
|
||||||
|
db.execSQL("DROP TABLE `ItemState`")
|
||||||
|
db.execSQL("ALTER TABLE `_new_ItemState` RENAME TO `ItemState`")
|
||||||
|
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_ItemState_remote_id_account_id` ON `ItemState` (`remote_id`, `account_id`)")
|
||||||
|
|
||||||
|
// remove guid, use remoteId local accounts
|
||||||
|
db.execSQL("Update Item set remoteId = guid Where remoteId is NULL")
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS `_new_Item` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `read_it_later` INTEGER NOT NULL, `remoteId` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||||
|
db.execSQL("INSERT INTO `_new_Item`(`id`, `title`, `description`, `clean_description`, `link`, `image_link`, `author`, `pub_date`, `content`, `feed_id`, `read_time`, `read`, `starred`, `read_it_later`, `remoteId`) SELECT `id`, `title`, `description`, `clean_description`, `link`, `image_link`, `author`, `pub_date`, `content`, `feed_id`, `read_time`, `read`, `starred`, `read_it_later`, `remoteId` FROM `Item`")
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS `Item`")
|
||||||
|
db.execSQL("ALTER TABLE `_new_Item` RENAME TO `Item`")
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS `index_Item_feed_id` ON `Item` (`feed_id`)")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ val dbModule = module {
|
||||||
|
|
||||||
single(createdAtStart = true) {
|
single(createdAtStart = true) {
|
||||||
Room.databaseBuilder(get(), Database::class.java, "readrops-db")
|
Room.databaseBuilder(get(), Database::class.java, "readrops-db")
|
||||||
.addMigrations(MigrationFrom1To2, MigrationFrom2To3)
|
.addMigrations(MigrationFrom1To2, MigrationFrom2To3, MigrationFrom3To4)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -64,6 +64,6 @@ abstract class ItemDao : BaseDao<Item> {
|
||||||
abstract fun selectFeedUnreadItemsCount(query: SupportSQLiteQuery):
|
abstract fun selectFeedUnreadItemsCount(query: SupportSQLiteQuery):
|
||||||
Flow<Map<@MapColumn(columnName = "feed_id") Int, @MapColumn(columnName = "item_count") Int>>
|
Flow<Map<@MapColumn(columnName = "feed_id") Int, @MapColumn(columnName = "item_count") Int>>
|
||||||
|
|
||||||
@Query("Select case When :guid In (Select guid From Item Inner Join Feed on Item.feed_id = Feed.id and account_id = :accountId) Then 1 else 0 end")
|
@Query("Select case When :remoteId In (Select Item.remoteId From Item Inner Join Feed on Item.feed_id = Feed.id and account_id = :accountId) Then 1 else 0 end")
|
||||||
abstract suspend fun itemExists(guid: String, accountId: Int): Boolean
|
abstract suspend fun itemExists(remoteId: String, accountId: Int): Boolean
|
||||||
}
|
}
|
|
@ -7,8 +7,16 @@ import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import org.joda.time.LocalDateTime
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
@Entity(foreignKeys = [ForeignKey(entity = Feed::class, parentColumns = ["id"],
|
@Entity(
|
||||||
childColumns = ["feed_id"], onDelete = ForeignKey.CASCADE)])
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Feed::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["feed_id"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
data class Item(
|
data class Item(
|
||||||
@PrimaryKey(autoGenerate = true) var id: Int = 0,
|
@PrimaryKey(autoGenerate = true) var id: Int = 0,
|
||||||
var title: String? = null,
|
var title: String? = null,
|
||||||
|
@ -20,7 +28,6 @@ data class Item(
|
||||||
@ColumnInfo(name = "pub_date") var pubDate: LocalDateTime? = null,
|
@ColumnInfo(name = "pub_date") var pubDate: LocalDateTime? = null,
|
||||||
var content: String? = null,
|
var content: String? = null,
|
||||||
@ColumnInfo(name = "feed_id", index = true) var feedId: Int = 0,
|
@ColumnInfo(name = "feed_id", index = true) var feedId: Int = 0,
|
||||||
@ColumnInfo(index = true) var guid: String? = null,
|
|
||||||
@ColumnInfo(name = "read_time") var readTime: Double = 0.0,
|
@ColumnInfo(name = "read_time") var readTime: Double = 0.0,
|
||||||
@ColumnInfo(name = "read") var isRead: Boolean = false,
|
@ColumnInfo(name = "read") var isRead: Boolean = false,
|
||||||
@ColumnInfo(name = "starred") var isStarred: Boolean = false,
|
@ColumnInfo(name = "starred") var isStarred: Boolean = false,
|
||||||
|
|
Loading…
Reference in New Issue