Rename
This commit is contained in:
1 parent
b86d90021c
commit
6c0757c91b
972 files changed
+6885
-7321
No files matched your search
@@ -0,0 +1,230 @@
|
||||
package io.legato.kazusa.data
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import io.legato.kazusa.data.dao.BookChapterDao
|
||||
import io.legato.kazusa.data.dao.BookDao
|
||||
import io.legato.kazusa.data.dao.BookGroupDao
|
||||
import io.legato.kazusa.data.dao.BookSourceDao
|
||||
import io.legato.kazusa.data.dao.BookmarkDao
|
||||
import io.legato.kazusa.data.dao.CacheDao
|
||||
import io.legato.kazusa.data.dao.CookieDao
|
||||
import io.legato.kazusa.data.dao.DictRuleDao
|
||||
import io.legato.kazusa.data.dao.HttpTTSDao
|
||||
import io.legato.kazusa.data.dao.KeyboardAssistsDao
|
||||
import io.legato.kazusa.data.dao.ReadRecordDao
|
||||
import io.legato.kazusa.data.dao.ReplaceRuleDao
|
||||
import io.legato.kazusa.data.dao.RssArticleDao
|
||||
import io.legato.kazusa.data.dao.RssReadRecordDao
|
||||
import io.legato.kazusa.data.dao.RssSourceDao
|
||||
import io.legato.kazusa.data.dao.RssStarDao
|
||||
import io.legato.kazusa.data.dao.RuleSubDao
|
||||
import io.legato.kazusa.data.dao.SearchBookDao
|
||||
import io.legato.kazusa.data.dao.SearchKeywordDao
|
||||
import io.legato.kazusa.data.dao.ServerDao
|
||||
import io.legato.kazusa.data.dao.TxtTocRuleDao
|
||||
import io.legato.kazusa.data.entities.Book
|
||||
import io.legato.kazusa.data.entities.BookChapter
|
||||
import io.legato.kazusa.data.entities.BookGroup
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.data.entities.BookSourcePart
|
||||
import io.legato.kazusa.data.entities.Bookmark
|
||||
import io.legato.kazusa.data.entities.Cache
|
||||
import io.legato.kazusa.data.entities.Cookie
|
||||
import io.legato.kazusa.data.entities.DictRule
|
||||
import io.legato.kazusa.data.entities.HttpTTS
|
||||
import io.legato.kazusa.data.entities.KeyboardAssist
|
||||
import io.legato.kazusa.data.entities.ReadRecord
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.data.entities.RssArticle
|
||||
import io.legato.kazusa.data.entities.RssReadRecord
|
||||
import io.legato.kazusa.data.entities.RssSource
|
||||
import io.legato.kazusa.data.entities.RssStar
|
||||
import io.legato.kazusa.data.entities.RuleSub
|
||||
import io.legato.kazusa.data.entities.SearchBook
|
||||
import io.legato.kazusa.data.entities.SearchKeyword
|
||||
import io.legato.kazusa.data.entities.Server
|
||||
import io.legato.kazusa.data.entities.TxtTocRule
|
||||
import io.legato.kazusa.help.DefaultData
|
||||
import org.intellij.lang.annotations.Language
|
||||
import splitties.init.appCtx
|
||||
import java.util.Locale
|
||||
|
||||
val appDb by lazy {
|
||||
Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME)
|
||||
.fallbackToDestructiveMigrationFrom(false, 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
||||
.addMigrations(*DatabaseMigrations.migrations)
|
||||
.allowMainThreadQueries()
|
||||
.addCallback(AppDatabase.dbCallback)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Database(
|
||||
version = 75,
|
||||
exportSchema = true,
|
||||
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
|
||||
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
|
||||
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
|
||||
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
|
||||
RuleSub::class, DictRule::class, KeyboardAssist::class, Server::class],
|
||||
views = [BookSourcePart::class],
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 43, to = 44),
|
||||
AutoMigration(from = 44, to = 45),
|
||||
AutoMigration(from = 45, to = 46),
|
||||
AutoMigration(from = 46, to = 47),
|
||||
AutoMigration(from = 47, to = 48),
|
||||
AutoMigration(from = 48, to = 49),
|
||||
AutoMigration(from = 49, to = 50),
|
||||
AutoMigration(from = 50, to = 51),
|
||||
AutoMigration(from = 51, to = 52),
|
||||
AutoMigration(from = 52, to = 53),
|
||||
AutoMigration(from = 53, to = 54),
|
||||
AutoMigration(from = 54, to = 55, spec = DatabaseMigrations.Migration_54_55::class),
|
||||
AutoMigration(from = 55, to = 56),
|
||||
AutoMigration(from = 56, to = 57),
|
||||
AutoMigration(from = 57, to = 58),
|
||||
AutoMigration(from = 58, to = 59),
|
||||
AutoMigration(from = 59, to = 60),
|
||||
AutoMigration(from = 60, to = 61),
|
||||
AutoMigration(from = 61, to = 62),
|
||||
AutoMigration(from = 62, to = 63),
|
||||
AutoMigration(from = 63, to = 64),
|
||||
AutoMigration(from = 64, to = 65, spec = DatabaseMigrations.Migration_64_65::class),
|
||||
AutoMigration(from = 65, to = 66),
|
||||
AutoMigration(from = 66, to = 67),
|
||||
AutoMigration(from = 67, to = 68),
|
||||
AutoMigration(from = 68, to = 69),
|
||||
AutoMigration(from = 69, to = 70),
|
||||
AutoMigration(from = 70, to = 71),
|
||||
AutoMigration(from = 71, to = 72),
|
||||
AutoMigration(from = 72, to = 73),
|
||||
AutoMigration(from = 73, to = 74),
|
||||
AutoMigration(from = 74, to = 75),
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract val bookDao: BookDao
|
||||
abstract val bookGroupDao: BookGroupDao
|
||||
abstract val bookSourceDao: BookSourceDao
|
||||
abstract val bookChapterDao: BookChapterDao
|
||||
abstract val replaceRuleDao: ReplaceRuleDao
|
||||
abstract val searchBookDao: SearchBookDao
|
||||
abstract val searchKeywordDao: SearchKeywordDao
|
||||
abstract val rssSourceDao: RssSourceDao
|
||||
abstract val bookmarkDao: BookmarkDao
|
||||
abstract val rssArticleDao: RssArticleDao
|
||||
abstract val rssStarDao: RssStarDao
|
||||
abstract val rssReadRecordDao: RssReadRecordDao
|
||||
abstract val cookieDao: CookieDao
|
||||
abstract val txtTocRuleDao: TxtTocRuleDao
|
||||
abstract val readRecordDao: ReadRecordDao
|
||||
abstract val httpTTSDao: HttpTTSDao
|
||||
abstract val cacheDao: CacheDao
|
||||
abstract val ruleSubDao: RuleSubDao
|
||||
abstract val dictRuleDao: DictRuleDao
|
||||
abstract val keyboardAssistsDao: KeyboardAssistsDao
|
||||
abstract val serverDao: ServerDao
|
||||
|
||||
companion object {
|
||||
|
||||
const val DATABASE_NAME = "legado.db"
|
||||
|
||||
const val BOOK_TABLE_NAME = "books"
|
||||
const val BOOK_SOURCE_TABLE_NAME = "book_sources"
|
||||
const val RSS_SOURCE_TABLE_NAME = "rssSources"
|
||||
|
||||
val dbCallback = object : Callback() {
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
db.setLocale(Locale.CHINESE)
|
||||
}
|
||||
|
||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
||||
@Language("sql")
|
||||
val insertBookGroupAllSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', show)
|
||||
select ${BookGroup.IdAll}, '全部', -10, 1
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdAll})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupAllSql)
|
||||
@Language("sql")
|
||||
val insertBookGroupLocalSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', enableRefresh, show)
|
||||
select ${BookGroup.IdLocal}, '本地', -9, 0, 1
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdLocal})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupLocalSql)
|
||||
@Language("sql")
|
||||
val insertBookGroupMusicSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', show)
|
||||
select ${BookGroup.IdAudio}, '音频', -8, 1
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdAudio})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupMusicSql)
|
||||
@Language("sql")
|
||||
val insertBookGroupNetNoneGroupSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', show)
|
||||
select ${BookGroup.IdNetNone}, '网络未分组', -7, 1
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdNetNone})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupNetNoneGroupSql)
|
||||
@Language("sql")
|
||||
val insertBookGroupLocalNoneGroupSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', show)
|
||||
select ${BookGroup.IdLocalNone}, '本地未分组', -6, 0
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdLocalNone})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupLocalNoneGroupSql)
|
||||
@Language("sql")
|
||||
val insertBookGroupErrorSql = """
|
||||
insert into book_groups(groupId, groupName, 'order', show)
|
||||
select ${BookGroup.IdError}, '更新失败', -1, 1
|
||||
where not exists (select * from book_groups where groupId = ${BookGroup.IdError})
|
||||
""".trimIndent()
|
||||
db.execSQL(insertBookGroupErrorSql)
|
||||
@Language("sql")
|
||||
val upBookSourceLoginUiSql =
|
||||
"update book_sources set loginUi = null where loginUi = 'null'"
|
||||
db.execSQL(upBookSourceLoginUiSql)
|
||||
@Language("sql")
|
||||
val upRssSourceLoginUiSql =
|
||||
"update rssSources set loginUi = null where loginUi = 'null'"
|
||||
db.execSQL(upRssSourceLoginUiSql)
|
||||
@Language("sql")
|
||||
val upHttpTtsLoginUiSql =
|
||||
"update httpTTS set loginUi = null where loginUi = 'null'"
|
||||
db.execSQL(upHttpTtsLoginUiSql)
|
||||
@Language("sql")
|
||||
val upHttpTtsConcurrentRateSql =
|
||||
"update httpTTS set concurrentRate = '0' where concurrentRate is null"
|
||||
db.execSQL(upHttpTtsConcurrentRateSql)
|
||||
db.query("select * from keyboardAssists order by serialNo").use {
|
||||
if (it.count == 0) {
|
||||
DefaultData.keyboardAssists.forEach { keyboardAssist ->
|
||||
val contentValues = ContentValues().apply {
|
||||
put("type", keyboardAssist.type)
|
||||
put("key", keyboardAssist.key)
|
||||
put("value", keyboardAssist.value)
|
||||
put("serialNo", keyboardAssist.serialNo)
|
||||
}
|
||||
db.insert(
|
||||
"keyboardAssists",
|
||||
SQLiteDatabase.CONFLICT_REPLACE,
|
||||
contentValues
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package io.legato.kazusa.data
|
||||
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import io.legato.kazusa.constant.AppConst
|
||||
import io.legato.kazusa.constant.BookSourceType
|
||||
import io.legato.kazusa.constant.BookType
|
||||
|
||||
object DatabaseMigrations {
|
||||
|
||||
val migrations: Array<Migration> by lazy {
|
||||
arrayOf(
|
||||
migration_10_11, migration_11_12, migration_12_13, migration_13_14,
|
||||
migration_14_15, migration_15_17, migration_17_18, migration_18_19,
|
||||
migration_19_20, migration_20_21, migration_21_22, migration_22_23,
|
||||
migration_23_24, migration_24_25, migration_25_26, migration_26_27,
|
||||
migration_27_28, migration_28_29, migration_29_30, migration_30_31,
|
||||
migration_31_32, migration_32_33, migration_33_34, migration_34_35,
|
||||
migration_35_36, migration_36_37, migration_37_38, migration_38_39,
|
||||
migration_39_40, migration_40_41, migration_41_42, migration_42_43,
|
||||
)
|
||||
}
|
||||
|
||||
private val migration_10_11 = object : Migration(10, 11) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DROP TABLE txtTocRules")
|
||||
database.execSQL(
|
||||
"""CREATE TABLE txtTocRules(id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL,
|
||||
enable INTEGER NOT NULL, PRIMARY KEY (id))"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_11_12 = object : Migration(11, 12) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE rssSources ADD style TEXT ")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_12_13 = object : Migration(12, 13) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0 ")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_13_14 = object : Migration(13, 14) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
|
||||
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT,
|
||||
`customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL,
|
||||
`latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL,
|
||||
`totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL,
|
||||
`durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL,
|
||||
`originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))"""
|
||||
)
|
||||
database.execSQL("INSERT INTO books_new select * from books ")
|
||||
database.execSQL("DROP TABLE books")
|
||||
database.execSQL("ALTER TABLE books_new RENAME TO books")
|
||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_14_15 = object : Migration(14, 15) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE bookmarks ADD bookAuthor TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_15_17 = object : Migration(15, 17) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_17_18 = object : Migration(17, 18) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_18_19 = object : Migration(18, 19) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`androidId`, `bookName`))"""
|
||||
)
|
||||
database.execSQL("INSERT INTO readRecordNew(androidId, bookName, readTime) select '${AppConst.androidId}' as androidId, bookName, readTime from readRecord")
|
||||
database.execSQL("DROP TABLE readRecord")
|
||||
database.execSQL("ALTER TABLE readRecordNew RENAME TO readRecord")
|
||||
}
|
||||
}
|
||||
private val migration_19_20 = object : Migration(19, 20) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE book_sources ADD bookSourceComment TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_20_21 = object : Migration(20, 21) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE book_groups ADD show INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_21_22 = object : Migration(21, 22) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
|
||||
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT,
|
||||
`coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL,
|
||||
`group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL,
|
||||
`lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL,
|
||||
`durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL,
|
||||
`order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))"""
|
||||
)
|
||||
database.execSQL(
|
||||
"""INSERT INTO books_new select `bookUrl`, `tocUrl`, `origin`, `originName`, `name`, `author`, `kind`, `customTag`, `coverUrl`,
|
||||
`customCoverUrl`, `intro`, `customIntro`, `charset`, `type`, `group`, `latestChapterTitle`, `latestChapterTime`, `lastCheckTime`,
|
||||
`lastCheckCount`, `totalChapterNum`, `durChapterTitle`, `durChapterIndex`, `durChapterPos`, `durChapterTime`, `wordCount`, `canUpdate`,
|
||||
`order`, `originOrder`, `variable`, null
|
||||
from books"""
|
||||
)
|
||||
database.execSQL("DROP TABLE books")
|
||||
database.execSQL("ALTER TABLE books_new RENAME TO books")
|
||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_22_23 = object : Migration(22, 23) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE chapters ADD baseUrl TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_23_24 = object : Migration(23, 24) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `caches` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))")
|
||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `caches` (`key`)")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_24_25 = object : Migration(24, 25) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `sourceSubs`
|
||||
(`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`id`))"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_25_26 = object : Migration(25, 26) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `ruleSubs` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL,
|
||||
`customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))"""
|
||||
)
|
||||
database.execSQL(" insert into `ruleSubs` select *, 0, 0 from `sourceSubs` ")
|
||||
database.execSQL("DROP TABLE `sourceSubs`")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_26_27 = object : Migration(26, 27) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(" ALTER TABLE rssSources ADD singleUrl INTEGER NOT NULL DEFAULT 0 ")
|
||||
database.execSQL(
|
||||
"""CREATE TABLE IF NOT EXISTS `bookmarks1` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL,
|
||||
`bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL,
|
||||
`bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))"""
|
||||
)
|
||||
database.execSQL(
|
||||
"""insert into `bookmarks1`
|
||||
select `time`, `bookUrl`, `bookName`, `bookAuthor`, `chapterIndex`, `pageIndex`, `chapterName`, '', `content`
|
||||
from bookmarks"""
|
||||
)
|
||||
database.execSQL(" DROP TABLE `bookmarks` ")
|
||||
database.execSQL(" ALTER TABLE bookmarks1 RENAME TO bookmarks ")
|
||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `bookmarks` (`time`)")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_27_28 = object : Migration(27, 28) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE rssArticles ADD variable TEXT")
|
||||
database.execSQL("ALTER TABLE rssStars ADD variable TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_28_29 = object : Migration(28, 29) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE rssSources ADD sourceComment TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_29_30 = object : Migration(29, 30) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE chapters ADD `startFragmentId` TEXT")
|
||||
database.execSQL("ALTER TABLE chapters ADD `endFragmentId` TEXT")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `epubChapters`
|
||||
(`bookUrl` TEXT NOT NULL, `href` TEXT NOT NULL, `parentHref` TEXT,
|
||||
PRIMARY KEY(`bookUrl`, `href`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )
|
||||
"""
|
||||
)
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_epubChapters_bookUrl` ON `epubChapters` (`bookUrl`)")
|
||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `epubChapters` (`bookUrl`, `href`)")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_30_31 = object : Migration(30, 31) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE readRecord RENAME TO readRecord1")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `readRecord` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))
|
||||
"""
|
||||
)
|
||||
database.execSQL("insert into readRecord (deviceId, bookName, readTime) select androidId, bookName, readTime from readRecord1")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_31_32 = object : Migration(31, 32) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("DROP TABLE `epubChapters`")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_32_33 = object : Migration(32, 33) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE bookmarks RENAME TO bookmarks_old")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `bookmarks` (`time` INTEGER NOT NULL,
|
||||
`bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL,
|
||||
`chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL,
|
||||
`content` TEXT NOT NULL, PRIMARY KEY(`time`))
|
||||
"""
|
||||
)
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `bookmarks` (`bookName`, `bookAuthor`)
|
||||
"""
|
||||
)
|
||||
database.execSQL(
|
||||
"""
|
||||
insert into bookmarks (time, bookName, bookAuthor, chapterIndex, chapterPos, chapterName, bookText, content)
|
||||
select time, ifNull(b.name, bookName) bookName, ifNull(b.author, bookAuthor) bookAuthor,
|
||||
chapterIndex, chapterPos, chapterName, bookText, content from bookmarks_old o
|
||||
left join books b on o.bookUrl = b.bookUrl
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_33_34 = object : Migration(33, 34) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `book_groups` ADD `cover` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_34_35 = object : Migration(34, 35) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `book_sources` ADD `concurrentRate` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_35_36 = object : Migration(35, 36) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `book_sources` ADD `loginUi` TEXT")
|
||||
database.execSQL("ALTER TABLE `book_sources` ADD`loginCheckJs` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_36_37 = object : Migration(36, 37) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `rssSources` ADD `loginUrl` TEXT")
|
||||
database.execSQL("ALTER TABLE `rssSources` ADD `loginUi` TEXT")
|
||||
database.execSQL("ALTER TABLE `rssSources` ADD `loginCheckJs` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_37_38 = object : Migration(37, 38) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `book_sources` ADD `respondTime` INTEGER NOT NULL DEFAULT 180000")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_38_39 = object : Migration(38, 39) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `rssSources` ADD `concurrentRate` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_39_40 = object : Migration(39, 40) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `chapters` ADD `isVip` INTEGER NOT NULL DEFAULT 0")
|
||||
database.execSQL("ALTER TABLE `chapters` ADD `isPay` INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_40_41 = object : Migration(40, 41) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `httpTTS` ADD `loginUrl` TEXT")
|
||||
database.execSQL("ALTER TABLE `httpTTS` ADD `loginUi` TEXT")
|
||||
database.execSQL("ALTER TABLE `httpTTS` ADD `loginCheckJs` TEXT")
|
||||
database.execSQL("ALTER TABLE `httpTTS` ADD `header` TEXT")
|
||||
database.execSQL("ALTER TABLE `httpTTS` ADD `concurrentRate` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_41_42 = object : Migration(41, 42) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE 'httpTTS' ADD `contentType` TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val migration_42_43 = object : Migration(42, 43) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `chapters` ADD `isVolume` INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("ClassName")
|
||||
class Migration_54_55 : AutoMigrationSpec {
|
||||
|
||||
override fun onPostMigrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"""
|
||||
update books set type = ${BookType.audio}
|
||||
where type = ${BookSourceType.audio}
|
||||
""".trimIndent()
|
||||
)
|
||||
db.execSQL(
|
||||
"""
|
||||
update books set type = ${BookType.image}
|
||||
where type = ${BookSourceType.image}
|
||||
""".trimIndent()
|
||||
)
|
||||
db.execSQL(
|
||||
"""
|
||||
update books set type = ${BookType.webFile}
|
||||
where type = ${BookSourceType.file}
|
||||
""".trimIndent()
|
||||
)
|
||||
db.execSQL(
|
||||
"""
|
||||
update books set type = ${BookType.text}
|
||||
where type = ${BookSourceType.default}
|
||||
""".trimIndent()
|
||||
)
|
||||
db.execSQL(
|
||||
"""
|
||||
update books set type = type | ${BookType.local}
|
||||
where origin like '${BookType.localTag}%' or origin like '${BookType.webDavTag}%'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Suppress("ClassName")
|
||||
@DeleteColumn(
|
||||
tableName = "book_sources",
|
||||
columnName = "enabledReview"
|
||||
)
|
||||
class Migration_64_65 : AutoMigrationSpec
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# 存储数据用
|
||||
|
||||
* dao 数据操作
|
||||
* entities 数据模型
|
||||
* \Book 书籍信息
|
||||
* \BookChapter 目录信息
|
||||
* \BookGroup 书籍分组
|
||||
* \Bookmark 书签
|
||||
* \BookSource 书源
|
||||
* \Cookie http cookie
|
||||
* \ReplaceRule 替换规则
|
||||
* \RssArticle rss条目
|
||||
* \RssReadRecord rss阅读记录
|
||||
* \RssSource rss源
|
||||
* \RssStar rss收藏
|
||||
* \SearchBook 搜索结果
|
||||
* \SearchKeyword 搜索关键字
|
||||
* \TxtTocRule txt文件目录规则
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.data.entities.BookChapter
|
||||
|
||||
@Dao
|
||||
interface BookChapterDao {
|
||||
|
||||
@Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%' order by `index`")
|
||||
fun search(bookUrl: String, key: String): List<BookChapter>
|
||||
|
||||
@Query("SELECT * FROM chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end and title like '%'||:key||'%' order by `index`")
|
||||
fun search(bookUrl: String, key: String, start: Int, end: Int): List<BookChapter>
|
||||
|
||||
@Query("select * from chapters where bookUrl = :bookUrl order by `index`")
|
||||
fun getChapterList(bookUrl: String): List<BookChapter>
|
||||
|
||||
@Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end order by `index`")
|
||||
fun getChapterList(bookUrl: String, start: Int, end: Int): List<BookChapter>
|
||||
|
||||
@Query("select * from chapters where bookUrl = :bookUrl and `index` = :index")
|
||||
fun getChapter(bookUrl: String, index: Int): BookChapter?
|
||||
|
||||
@Query("select * from chapters where bookUrl = :bookUrl and `title` = :title")
|
||||
fun getChapter(bookUrl: String, title: String): BookChapter?
|
||||
|
||||
@Query("select count(url) from chapters where bookUrl = :bookUrl")
|
||||
fun getChapterCount(bookUrl: String): Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg bookChapter: BookChapter)
|
||||
|
||||
@Update
|
||||
fun update(vararg bookChapter: BookChapter)
|
||||
|
||||
@Query("delete from chapters where bookUrl = :bookUrl")
|
||||
fun delByBook(bookUrl: String)
|
||||
|
||||
@Query("update chapters set wordCount = :wordCount where bookUrl = :bookUrl and url = :url")
|
||||
fun upWordCount(bookUrl: String, url: String, wordCount: String)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.data.entities.Book
|
||||
import io.legato.kazusa.data.entities.BookGroup
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.help.book.isNotShelf
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Dao
|
||||
interface BookDao {
|
||||
|
||||
fun flowByGroup(groupId: Long): Flow<List<Book>> {
|
||||
return when (groupId) {
|
||||
BookGroup.IdRoot -> flowRoot()
|
||||
BookGroup.IdAll -> flowAll()
|
||||
BookGroup.IdLocal -> flowLocal()
|
||||
BookGroup.IdAudio -> flowAudio()
|
||||
BookGroup.IdNetNone -> flowNetNoGroup()
|
||||
BookGroup.IdLocalNone -> flowLocalNoGroup()
|
||||
BookGroup.IdError -> flowUpdateError()
|
||||
else -> flowByUserGroup(groupId)
|
||||
}.map { list ->
|
||||
list.filterNot { it.isNotShelf }
|
||||
}
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select * from books where type & ${BookType.text} > 0
|
||||
and type & ${BookType.local} = 0
|
||||
and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
|
||||
and (select show from book_groups where groupId = ${BookGroup.IdNetNone}) != 1
|
||||
"""
|
||||
)
|
||||
fun flowRoot(): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books order by durChapterTime desc")
|
||||
fun flowAll(): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books WHERE type & ${BookType.audio} > 0")
|
||||
fun flowAudio(): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books WHERE type & ${BookType.local} > 0")
|
||||
fun flowLocal(): Flow<List<Book>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select * from books where type & ${BookType.audio} = 0 and type & ${BookType.local} = 0
|
||||
and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
|
||||
"""
|
||||
)
|
||||
fun flowNetNoGroup(): Flow<List<Book>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select * from books where type & ${BookType.local} > 0
|
||||
and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
|
||||
"""
|
||||
)
|
||||
fun flowLocalNoGroup(): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books WHERE (`group` & :group) > 0")
|
||||
fun flowByUserGroup(group: Long): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
|
||||
fun flowSearch(key: String): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books where type & ${BookType.updateError} > 0 order by durChapterTime desc")
|
||||
fun flowUpdateError(): Flow<List<Book>>
|
||||
|
||||
@Query("SELECT * FROM books WHERE (`group` & :group) > 0")
|
||||
fun getBooksByGroup(group: Long): List<Book>
|
||||
|
||||
@Query("SELECT * FROM books WHERE `name` in (:names)")
|
||||
fun findByName(vararg names: String): List<Book>
|
||||
|
||||
@Query("select * from books where originName = :fileName")
|
||||
fun getBookByFileName(fileName: String): Book?
|
||||
|
||||
@Query("SELECT * FROM books WHERE bookUrl = :bookUrl")
|
||||
fun getBook(bookUrl: String): Book?
|
||||
|
||||
@Query("SELECT * FROM books WHERE name = :name and author = :author")
|
||||
fun getBook(name: String, author: String): Book?
|
||||
|
||||
@Query("""select distinct bs.* from books, book_sources bs
|
||||
where origin == bookSourceUrl and origin not like '${BookType.localTag}%'
|
||||
and origin not like '${BookType.webDavTag}%'""")
|
||||
fun getAllUseBookSource(): List<BookSource>
|
||||
|
||||
@Query("SELECT * FROM books WHERE name = :name and origin = :origin")
|
||||
fun getBookByOrigin(name: String, origin: String): Book?
|
||||
|
||||
@get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups)")
|
||||
val noGroupSize: Int
|
||||
|
||||
@get:Query("SELECT * FROM books where type & ${BookType.local} = 0")
|
||||
val webBooks: List<Book>
|
||||
|
||||
@get:Query("SELECT * FROM books where type & ${BookType.local} = 0 and canUpdate = 1")
|
||||
val hasUpdateBooks: List<Book>
|
||||
|
||||
@get:Query("SELECT * FROM books")
|
||||
val all: List<Book>
|
||||
|
||||
@Query("SELECT * FROM books where type & :type > 0 and type & ${BookType.local} = 0")
|
||||
fun getByTypeOnLine(type: Int): List<Book>
|
||||
|
||||
@get:Query("SELECT * FROM books where type & ${BookType.text} > 0 ORDER BY durChapterTime DESC limit 1")
|
||||
val lastReadBook: Book?
|
||||
|
||||
@get:Query("SELECT bookUrl FROM books")
|
||||
val allBookUrls: List<String>
|
||||
|
||||
@get:Query("SELECT COUNT(*) FROM books")
|
||||
val allBookCount: Int
|
||||
|
||||
@get:Query("select min(`order`) from books")
|
||||
val minOrder: Int
|
||||
|
||||
@get:Query("select max(`order`) from books")
|
||||
val maxOrder: Int
|
||||
|
||||
@Query("select exists(select 1 from books where bookUrl = :bookUrl)")
|
||||
fun has(bookUrl: String): Boolean
|
||||
|
||||
@Query("select exists(select 1 from books where name = :name and author = :author)")
|
||||
fun has(name: String, author: String): Boolean
|
||||
|
||||
@Query(
|
||||
"""select exists(select 1 from books where type & ${BookType.local} > 0
|
||||
and (originName = :fileName or (origin != '${BookType.localTag}' and origin like '%' || :fileName)))"""
|
||||
)
|
||||
fun hasFile(fileName: String): Boolean
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg book: Book)
|
||||
|
||||
@Update
|
||||
fun update(vararg book: Book)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg book: Book)
|
||||
|
||||
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
|
||||
fun upProgress(bookUrl: String, pos: Int)
|
||||
|
||||
@Query("update books set `group` = :newGroupId where `group` = :oldGroupId")
|
||||
fun upGroup(oldGroupId: Long, newGroupId: Long)
|
||||
|
||||
@Query("update books set `group` = `group` - :group where `group` & :group > 0")
|
||||
fun removeGroup(group: Long)
|
||||
|
||||
@Query("delete from books where type & ${BookType.notShelf} > 0")
|
||||
fun deleteNotShelfBook()
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.data.entities.BookGroup
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface BookGroupDao {
|
||||
|
||||
@Query("select * from book_groups where groupId = :id")
|
||||
fun getByID(id: Long): BookGroup?
|
||||
|
||||
@Query("select * from book_groups where groupName = :groupName")
|
||||
fun getByName(groupName: String): BookGroup?
|
||||
|
||||
@Query("SELECT * FROM book_groups ORDER BY `order`")
|
||||
fun flowAll(): Flow<List<BookGroup>>
|
||||
|
||||
@get:Query(
|
||||
"""
|
||||
with const as (SELECT sum(groupId) sumGroupId FROM book_groups where groupId > 0)
|
||||
SELECT book_groups.* FROM book_groups join const
|
||||
where show > 0
|
||||
and (
|
||||
(groupId >= 0 and exists (select 1 from books where `group` & book_groups.groupId > 0))
|
||||
or groupId = -1
|
||||
or (groupId = -2 and exists (select 1 from books where type & ${BookType.local} > 0))
|
||||
or (groupId = -3 and exists (select 1 from books where type & ${BookType.audio} > 0))
|
||||
or (groupId = -11 and exists (select 1 from books where type & ${BookType.updateError} > 0))
|
||||
or (groupId = -4
|
||||
and exists (
|
||||
select 1 from books
|
||||
where type & ${BookType.audio} = 0
|
||||
and type & ${BookType.local} = 0
|
||||
and const.sumGroupId & `group` = 0
|
||||
)
|
||||
)
|
||||
or (groupId = -5
|
||||
and exists (
|
||||
select 1 from books
|
||||
where type & ${BookType.audio} = 0
|
||||
and type & ${BookType.local} > 0
|
||||
and const.sumGroupId & `group` = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
ORDER BY `order`"""
|
||||
)
|
||||
val show: LiveData<List<BookGroup>>
|
||||
|
||||
@Query("SELECT * FROM book_groups where groupId >= 0 ORDER BY `order`")
|
||||
fun flowSelect(): Flow<List<BookGroup>>
|
||||
|
||||
@get:Query("SELECT sum(groupId) FROM book_groups where groupId >= 0")
|
||||
val idsSum: Long
|
||||
|
||||
@get:Query("SELECT MAX(`order`) FROM book_groups where groupId >= 0")
|
||||
val maxOrder: Int
|
||||
|
||||
@get:Query("SELECT * FROM book_groups ORDER BY `order`")
|
||||
val all: List<BookGroup>
|
||||
|
||||
@get:Query("select count(*) < 64 from book_groups where groupId >= 0 or groupId == ${Long.MIN_VALUE}")
|
||||
val canAddGroup: Boolean
|
||||
|
||||
@Query("update book_groups set show = 1 where groupId = :groupId")
|
||||
fun enableGroup(groupId: Long)
|
||||
|
||||
@Query("select groupName from book_groups where groupId > 0 and (groupId & :id) > 0")
|
||||
fun getGroupNames(id: Long): List<String>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg bookGroup: BookGroup)
|
||||
|
||||
@Update
|
||||
fun update(vararg bookGroup: BookGroup)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg bookGroup: BookGroup)
|
||||
|
||||
fun isInRules(id: Long): Boolean {
|
||||
if (id < 0) {
|
||||
return true
|
||||
}
|
||||
return id and (id - 1) == 0L
|
||||
}
|
||||
|
||||
fun getUnusedId(): Long {
|
||||
var id = 1L
|
||||
val idsSum = idsSum
|
||||
while (id and idsSum != 0L) {
|
||||
id = id.shl(1)
|
||||
}
|
||||
return id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.data.entities.BookSourcePart
|
||||
import io.legato.kazusa.utils.cnCompare
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Dao
|
||||
interface BookSourceDao {
|
||||
|
||||
@Query("select * from book_sources_part order by customOrder asc")
|
||||
fun flowAll(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select bp.*
|
||||
from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl
|
||||
where b.bookSourceName like '%' || :searchKey || '%'
|
||||
or b.bookSourceGroup like '%' || :searchKey || '%'
|
||||
or b.bookSourceUrl like '%' || :searchKey || '%'
|
||||
or b.bookSourceComment like '%' || :searchKey || '%'
|
||||
order by b.customOrder asc"""
|
||||
)
|
||||
fun flowSearch(searchKey: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceName like '%' || :searchKey || '%'
|
||||
or bookSourceGroup like '%' || :searchKey || '%'
|
||||
or bookSourceUrl like '%' || :searchKey || '%'
|
||||
or bookSourceComment like '%' || :searchKey || '%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun search(searchKey: String): List<BookSource>
|
||||
|
||||
@Query(
|
||||
"""select bp.*
|
||||
from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl
|
||||
where b.enabled = 1
|
||||
and (b.bookSourceName like '%' || :searchKey || '%'
|
||||
or b.bookSourceGroup like '%' || :searchKey || '%'
|
||||
or b.bookSourceUrl like '%' || :searchKey || '%'
|
||||
or b.bookSourceComment like '%' || :searchKey || '%')
|
||||
order by b.customOrder asc"""
|
||||
)
|
||||
fun flowSearchEnabled(searchKey: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where bookSourceGroup = :searchKey
|
||||
or bookSourceGroup like :searchKey || ',%'
|
||||
or bookSourceGroup like '%,' || :searchKey
|
||||
or bookSourceGroup like '%,' || :searchKey || ',%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun flowGroupSearch(searchKey: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceGroup = :searchKey
|
||||
or bookSourceGroup like :searchKey || ',%'
|
||||
or bookSourceGroup like '%,' || :searchKey
|
||||
or bookSourceGroup like '%,' || :searchKey || ',%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun groupSearch(searchKey: String): List<BookSource>
|
||||
|
||||
@Query("select * from book_sources_part where enabled = 1 order by customOrder asc")
|
||||
fun flowEnabled(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select * from book_sources_part where enabled = 0 order by customOrder asc")
|
||||
fun flowDisabled(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where enabledExplore = 1 and hasExploreUrl = 1 order by customOrder asc"""
|
||||
)
|
||||
fun flowExplore(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select * from book_sources_part where hasLoginUrl = 1 order by customOrder asc")
|
||||
fun flowLogin(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun flowNoGroup(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select * from book_sources_part where enabledExplore = 1 order by customOrder asc")
|
||||
fun flowEnabledExplore(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select * from book_sources_part where enabledExplore = 0 order by customOrder asc")
|
||||
fun flowDisabledExplore(): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where enabledExplore = 1
|
||||
and hasExploreUrl = 1
|
||||
and (bookSourceGroup like '%' || :key || '%'
|
||||
or bookSourceName like '%' || :key || '%')
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun flowExplore(key: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where enabledExplore = 1
|
||||
and hasExploreUrl = 1
|
||||
and (bookSourceGroup = :key
|
||||
or bookSourceGroup like :key || ',%'
|
||||
or bookSourceGroup like '%,' || :key
|
||||
or bookSourceGroup like '%,' || :key || ',%')
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun flowGroupExplore(key: String): Flow<List<BookSourcePart>>
|
||||
|
||||
@Query("select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
|
||||
fun flowGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"""select distinct bookSourceGroup from book_sources
|
||||
where enabled = 1 and trim(bookSourceGroup) <> ''"""
|
||||
)
|
||||
fun flowEnabledGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"""select distinct bookSourceGroup from book_sources
|
||||
where enabledExplore = 1
|
||||
and trim(exploreUrl) <> ''
|
||||
and trim(bookSourceGroup) <> ''
|
||||
order by customOrder"""
|
||||
)
|
||||
fun flowExploreGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceGroup like '%' || :group || '%' order by customOrder asc"""
|
||||
)
|
||||
fun getByGroup(group: String): List<BookSource>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where enabled = 1
|
||||
and (bookSourceGroup = :group
|
||||
or bookSourceGroup like :group || ',%'
|
||||
or bookSourceGroup like '%,' || :group
|
||||
or bookSourceGroup like '%,' || :group || ',%')
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun getEnabledByGroup(group: String): List<BookSource>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources_part
|
||||
where enabled = 1
|
||||
and (bookSourceGroup = :group
|
||||
or bookSourceGroup like :group || ',%'
|
||||
or bookSourceGroup like '%,' || :group
|
||||
or bookSourceGroup like '%,' || :group || ',%')
|
||||
order by customOrder asc"""
|
||||
)
|
||||
fun getEnabledPartByGroup(group: String): List<BookSourcePart>
|
||||
|
||||
@Query(
|
||||
"""select * from book_sources
|
||||
where bookUrlPattern != 'NONE' and bookSourceType = :type order by customOrder asc"""
|
||||
)
|
||||
fun getEnabledByType(type: Int): List<BookSource>
|
||||
|
||||
@Query("select * from book_sources where enabled = 1 and bookSourceUrl = :baseUrl")
|
||||
fun getBookSourceAddBook(baseUrl: String): BookSource?
|
||||
|
||||
@get:Query(
|
||||
"""select bp.*
|
||||
from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl
|
||||
where b.enabled = 1
|
||||
and trim(b.bookUrlPattern) <> ''
|
||||
and trim(b.bookUrlPattern) <> 'NONE'
|
||||
order by b.customOrder"""
|
||||
)
|
||||
val hasBookUrlPattern: List<BookSourcePart>
|
||||
|
||||
@get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''")
|
||||
val noGroup: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources order by customOrder asc")
|
||||
val all: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources_part order by customOrder asc")
|
||||
val allPart: List<BookSourcePart>
|
||||
|
||||
@get:Query("select * from book_sources where enabled = 1 order by customOrder")
|
||||
val allEnabled: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources_part where enabled = 1 order by customOrder asc")
|
||||
val allEnabledPart: List<BookSourcePart>
|
||||
|
||||
@get:Query("select * from book_sources where enabled = 0 order by customOrder")
|
||||
val allDisabled: List<BookSource>
|
||||
|
||||
@get:Query(
|
||||
"""select * from book_sources
|
||||
where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'"""
|
||||
)
|
||||
val allNoGroup: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where enabledExplore = 1 order by customOrder")
|
||||
val allEnabledExplore: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where enabledExplore = 0 order by customOrder")
|
||||
val allDisabledExplore: List<BookSource>
|
||||
|
||||
@get:Query("select * from book_sources where loginUrl is not null and loginUrl != ''")
|
||||
val allLogin: List<BookSource>
|
||||
|
||||
@get:Query(
|
||||
"""select bp.*
|
||||
from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl
|
||||
where b.enabled = 1 and b.bookSourceType = 0 order by b.customOrder"""
|
||||
)
|
||||
val allTextEnabledPart: List<BookSourcePart>
|
||||
|
||||
@get:Query(
|
||||
"""select distinct bookSourceGroup from book_sources
|
||||
where trim(bookSourceGroup) <> ''"""
|
||||
)
|
||||
val allGroupsUnProcessed: List<String>
|
||||
|
||||
@get:Query(
|
||||
"""select distinct bookSourceGroup from book_sources
|
||||
where enabled = 1 and trim(bookSourceGroup) <> ''"""
|
||||
)
|
||||
val allEnabledGroupsUnProcessed: List<String>
|
||||
|
||||
@Query("select * from book_sources where bookSourceUrl = :key")
|
||||
fun getBookSource(key: String): BookSource?
|
||||
|
||||
@Query("select * from book_sources_part where bookSourceUrl = :key")
|
||||
fun getBookSourcePart(key: String): BookSourcePart?
|
||||
|
||||
@Query("select count(*) from book_sources")
|
||||
fun allCount(): Int
|
||||
|
||||
@Query("SELECT EXISTS(select 1 from book_sources where bookSourceUrl = :key)")
|
||||
fun has(key: String): Boolean
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg bookSource: BookSource)
|
||||
|
||||
@Update
|
||||
fun update(vararg bookSource: BookSource)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg bookSource: BookSource)
|
||||
|
||||
@Query("delete from book_sources where bookSourceUrl = :key")
|
||||
fun delete(key: String)
|
||||
|
||||
@Transaction
|
||||
fun delete(bookSources: List<BookSourcePart>) {
|
||||
for (bs in bookSources) {
|
||||
delete(bs.bookSourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@get:Query("select min(customOrder) from book_sources")
|
||||
val minOrder: Int
|
||||
|
||||
@get:Query("select max(customOrder) from book_sources")
|
||||
val maxOrder: Int
|
||||
|
||||
@get:Query(
|
||||
"""select exists (select 1
|
||||
from book_sources group by customOrder having count(customOrder) > 1)"""
|
||||
)
|
||||
val hasDuplicateOrder: Boolean
|
||||
|
||||
@Query("update book_sources set enabled = :enable where bookSourceUrl = :bookSourceUrl")
|
||||
fun enable(bookSourceUrl: String, enable: Boolean)
|
||||
|
||||
@Transaction
|
||||
fun enable(enable: Boolean, bookSources: List<BookSourcePart>) {
|
||||
for (bs in bookSources) {
|
||||
enable(bs.bookSourceUrl, enable)
|
||||
}
|
||||
}
|
||||
|
||||
@Query("update book_sources set enabledExplore = :enable where bookSourceUrl = :bookSourceUrl")
|
||||
fun enableExplore(bookSourceUrl: String, enable: Boolean)
|
||||
|
||||
@Transaction
|
||||
fun enableExplore(enable: Boolean, bookSources: List<BookSourcePart>) {
|
||||
for (bs in bookSources) {
|
||||
enableExplore(bs.bookSourceUrl, enable)
|
||||
}
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""update book_sources
|
||||
set customOrder = :customOrder where bookSourceUrl = :bookSourceUrl"""
|
||||
)
|
||||
fun upOrder(bookSourceUrl: String, customOrder: Int)
|
||||
|
||||
@Transaction
|
||||
fun upOrder(bookSources: List<BookSourcePart>) {
|
||||
for (bs in bookSources) {
|
||||
upOrder(bs.bookSourceUrl, bs.customOrder)
|
||||
}
|
||||
}
|
||||
|
||||
fun upOrder(bookSource: BookSourcePart) {
|
||||
upOrder(bookSource.bookSourceUrl, bookSource.customOrder)
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""update book_sources
|
||||
set bookSourceGroup = :bookSourceGroup where bookSourceUrl = :bookSourceUrl"""
|
||||
)
|
||||
fun upGroup(bookSourceUrl: String, bookSourceGroup: String)
|
||||
|
||||
@Transaction
|
||||
fun upGroup(bookSources: List<BookSourcePart>) {
|
||||
for (bs in bookSources) {
|
||||
bs.bookSourceGroup?.let { upGroup(bs.bookSourceUrl, it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun dealGroups(list: List<String>): List<String> {
|
||||
val groups = linkedSetOf<String>()
|
||||
list.forEach {
|
||||
it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->
|
||||
groups.add(group)
|
||||
}
|
||||
}
|
||||
return groups.sortedWith { o1, o2 ->
|
||||
o1.cnCompare(o2)
|
||||
}
|
||||
}
|
||||
|
||||
fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)
|
||||
|
||||
fun allEnabledGroups(): List<String> = dealGroups(allEnabledGroupsUnProcessed)
|
||||
|
||||
fun flowGroups(): Flow<List<String>> {
|
||||
return flowGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
|
||||
fun flowExploreGroups(): Flow<List<String>> {
|
||||
return flowExploreGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
|
||||
fun flowEnabledGroups(): Flow<List<String>> {
|
||||
return flowEnabledGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.Bookmark
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@Dao
|
||||
interface BookmarkDao {
|
||||
|
||||
@get:Query(
|
||||
"""
|
||||
select * from bookmarks order by bookName collate localized, bookAuthor collate localized, chapterIndex, chapterPos
|
||||
"""
|
||||
)
|
||||
val all: List<Bookmark>
|
||||
|
||||
@Query("select * from bookmarks order by bookName collate localized, bookAuthor collate localized, chapterIndex, chapterPos")
|
||||
fun flowAll(): Flow<List<Bookmark>>
|
||||
|
||||
@Query(
|
||||
"""select * from bookmarks
|
||||
where bookName = :bookName and bookAuthor = :bookAuthor
|
||||
order by chapterIndex"""
|
||||
)
|
||||
fun flowByBook(bookName: String, bookAuthor: String): Flow<List<Bookmark>>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM bookmarks
|
||||
where bookName = :bookName and bookAuthor = :bookAuthor
|
||||
and chapterName like '%'||:key||'%' or content like '%'||:key||'%'
|
||||
order by chapterIndex"""
|
||||
)
|
||||
fun flowSearch(bookName: String, bookAuthor: String, key: String): Flow<List<Bookmark>>
|
||||
|
||||
@Query(
|
||||
"""select * from bookmarks
|
||||
where bookName = :bookName and bookAuthor = :bookAuthor
|
||||
order by chapterIndex"""
|
||||
)
|
||||
fun getByBook(bookName: String, bookAuthor: String): List<Bookmark>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM bookmarks
|
||||
where bookName = :bookName and bookAuthor = :bookAuthor
|
||||
and chapterName like '%'||:key||'%' or content like '%'||:key||'%'
|
||||
order by chapterIndex"""
|
||||
)
|
||||
fun search(bookName: String, bookAuthor: String, key: String): List<Bookmark>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg bookmark: Bookmark)
|
||||
|
||||
@Update
|
||||
fun update(bookmark: Bookmark)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg bookmark: Bookmark)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import io.legato.kazusa.data.entities.Cache
|
||||
|
||||
@Dao
|
||||
interface CacheDao {
|
||||
|
||||
@Query("select * from caches where `key` = :key")
|
||||
fun get(key: String): Cache?
|
||||
|
||||
@Query("select value from caches where `key` = :key and (deadline = 0 or deadline > :now)")
|
||||
fun get(key: String, now: Long): String?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg cache: Cache)
|
||||
|
||||
@Query("delete from caches where `key` = :key")
|
||||
fun delete(key: String)
|
||||
|
||||
@Query(
|
||||
"""delete from caches where `key` like 'v_' || :key || '_%'
|
||||
or `key` = 'userInfo_' || :key
|
||||
or `key` = 'loginHeader_' || :key
|
||||
or `key` = 'sourceVariable_' || :key"""
|
||||
)
|
||||
fun deleteSourceVariables(key: String)
|
||||
|
||||
@Query("delete from caches where deadline > 0 and deadline < :now")
|
||||
fun clearDeadline(now: Long)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.Cookie
|
||||
|
||||
@Dao
|
||||
interface CookieDao {
|
||||
|
||||
@Query("SELECT * FROM cookies Where url = :url")
|
||||
fun get(url: String): Cookie?
|
||||
|
||||
@Query("select * from cookies where url like '%|%'")
|
||||
fun getOkHttpCookies(): List<Cookie>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg cookie: Cookie)
|
||||
|
||||
@Update
|
||||
fun update(vararg cookie: Cookie)
|
||||
|
||||
@Query("delete from cookies where url = :url")
|
||||
fun delete(url: String)
|
||||
|
||||
@Query("delete from cookies where url like '%|%'")
|
||||
fun deleteOkHttp()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.DictRule
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@Dao
|
||||
interface DictRuleDao {
|
||||
|
||||
@get:Query("select * from dictRules order by sortNumber")
|
||||
val all: List<DictRule>
|
||||
|
||||
@get:Query("select * from dictRules where enabled = 1 order by sortNumber")
|
||||
val enabled: List<DictRule>
|
||||
|
||||
@Query("select * from dictRules order by sortNumber")
|
||||
fun flowAll(): Flow<List<DictRule>>
|
||||
|
||||
@Query("select * from dictRules where name = :name")
|
||||
fun getByName(name: String): DictRule?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg dictRule: DictRule)
|
||||
|
||||
@Update
|
||||
fun update(vararg dictRule: DictRule)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg dictRule: DictRule)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.HttpTTS
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface HttpTTSDao {
|
||||
|
||||
@get:Query("select * from httpTTS order by name")
|
||||
val all: List<HttpTTS>
|
||||
|
||||
@Query("select * from httpTTS order by name")
|
||||
fun flowAll(): Flow<List<HttpTTS>>
|
||||
|
||||
@get:Query("select count(*) from httpTTS")
|
||||
val count: Int
|
||||
|
||||
@Query("select * from httpTTS where id = :id")
|
||||
fun get(id: Long): HttpTTS?
|
||||
|
||||
@Query("select name from httpTTS where id = :id")
|
||||
fun getName(id: Long): String?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg httpTTS: HttpTTS)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg httpTTS: HttpTTS)
|
||||
|
||||
@Update
|
||||
fun update(vararg httpTTS: HttpTTS)
|
||||
|
||||
@Query("delete from httpTTS where id < 0")
|
||||
fun deleteDefault()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.KeyboardAssist
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface KeyboardAssistsDao {
|
||||
|
||||
@get:Query("select * from keyboardAssists order by serialNo")
|
||||
val all: List<KeyboardAssist>
|
||||
|
||||
@Query("select * from keyboardAssists where type = :type order by serialNo")
|
||||
fun getByType(type: Int): List<KeyboardAssist>
|
||||
|
||||
@get:Query("select * from keyboardAssists order by serialNo")
|
||||
val flowAll: Flow<List<KeyboardAssist>>
|
||||
|
||||
@Query("select * from keyboardAssists where type = :type order by serialNo")
|
||||
fun flowByType(type: Int): Flow<List<KeyboardAssist>>
|
||||
|
||||
@get:Query("select max(serialNo) from keyboardAssists order by serialNo")
|
||||
val maxSerialNo: Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg keyboardAssist: KeyboardAssist)
|
||||
|
||||
@Update
|
||||
fun update(vararg keyboardAssist: KeyboardAssist)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg keyboardAssist: KeyboardAssist)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.ReadRecord
|
||||
import io.legato.kazusa.data.entities.ReadRecordShow
|
||||
|
||||
@Dao
|
||||
interface ReadRecordDao {
|
||||
|
||||
@get:Query("select * from readRecord")
|
||||
val all: List<ReadRecord>
|
||||
|
||||
@get:Query(
|
||||
"""
|
||||
select bookName, sum(readTime) as readTime, max(lastRead) as lastRead
|
||||
from readRecord
|
||||
group by bookName
|
||||
order by bookName collate localized"""
|
||||
)
|
||||
val allShow: List<ReadRecordShow>
|
||||
|
||||
@get:Query("select sum(readTime) from readRecord")
|
||||
val allTime: Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select bookName, sum(readTime) as readTime, max(lastRead) as lastRead
|
||||
from readRecord
|
||||
where bookName like '%' || :searchKey || '%'
|
||||
group by bookName
|
||||
order by bookName collate localized"""
|
||||
)
|
||||
fun search(searchKey: String): List<ReadRecordShow>
|
||||
|
||||
@Query("select sum(readTime) from readRecord where bookName = :bookName")
|
||||
fun getReadTime(bookName: String): Long?
|
||||
|
||||
@Query("select readTime from readRecord where deviceId = :androidId and bookName = :bookName")
|
||||
fun getReadTime(androidId: String, bookName: String): Long?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg readRecord: ReadRecord)
|
||||
|
||||
@Update
|
||||
fun update(vararg record: ReadRecord)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg record: ReadRecord)
|
||||
|
||||
@Query("delete from readRecord")
|
||||
fun clear()
|
||||
|
||||
@Query("delete from readRecord where bookName = :bookName")
|
||||
fun deleteByName(bookName: String)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.utils.cnCompare
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
|
||||
@Dao
|
||||
interface ReplaceRuleDao {
|
||||
|
||||
@Query("SELECT * FROM replace_rules ORDER BY sortOrder ASC")
|
||||
fun flowAll(): Flow<List<ReplaceRule>>
|
||||
|
||||
@Query("SELECT * FROM replace_rules where `group` like :key or name like :key ORDER BY sortOrder ASC")
|
||||
fun flowSearch(key: String): Flow<List<ReplaceRule>>
|
||||
|
||||
@Query("SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC")
|
||||
fun flowGroupSearch(key: String): Flow<List<ReplaceRule>>
|
||||
|
||||
@Query("select `group` from replace_rules where `group` is not null and `group` <> ''")
|
||||
fun flowGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@Query("select * from replace_rules where `group` is null or trim(`group`) = '' or trim(`group`) like '%未分组%'")
|
||||
fun flowNoGroup(): Flow<List<ReplaceRule>>
|
||||
|
||||
@get:Query("SELECT MIN(sortOrder) FROM replace_rules")
|
||||
val minOrder: Int
|
||||
|
||||
@get:Query("SELECT MAX(sortOrder) FROM replace_rules")
|
||||
val maxOrder: Int
|
||||
|
||||
@get:Query("SELECT * FROM replace_rules ORDER BY sortOrder ASC")
|
||||
val all: List<ReplaceRule>
|
||||
|
||||
@get:Query("select distinct `group` from replace_rules where trim(`group`) <> ''")
|
||||
val allGroupsUnProcessed: List<String>
|
||||
|
||||
@get:Query("SELECT * FROM replace_rules WHERE isEnabled = 1 ORDER BY sortOrder ASC")
|
||||
val allEnabled: List<ReplaceRule>
|
||||
|
||||
@Query("SELECT * FROM replace_rules WHERE id = :id")
|
||||
fun findById(id: Long): ReplaceRule?
|
||||
|
||||
@Query("SELECT * FROM replace_rules WHERE id in (:ids)")
|
||||
fun findByIds(vararg ids: Long): List<ReplaceRule>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM replace_rules WHERE isEnabled = 1 and scopeContent = 1
|
||||
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')
|
||||
and (excludeScope is null or (excludeScope not LIKE '%' || :name || '%' and excludeScope not LIKE '%' || :origin || '%'))
|
||||
order by sortOrder"""
|
||||
)
|
||||
fun findEnabledByContentScope(name: String, origin: String): List<ReplaceRule>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM replace_rules WHERE isEnabled = 1 and scopeTitle = 1
|
||||
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')
|
||||
and (excludeScope is null or (excludeScope not LIKE '%' || :name || '%' and excludeScope not LIKE '%' || :origin || '%'))
|
||||
order by sortOrder"""
|
||||
)
|
||||
fun findEnabledByTitleScope(name: String, origin: String): List<ReplaceRule>
|
||||
|
||||
@Query("select * from replace_rules where `group` like '%' || :group || '%'")
|
||||
fun getByGroup(group: String): List<ReplaceRule>
|
||||
|
||||
@get:Query("select * from replace_rules where `group` is null or `group` = ''")
|
||||
val noGroup: List<ReplaceRule>
|
||||
|
||||
@get:Query("SELECT COUNT(*) - SUM(isEnabled) FROM replace_rules")
|
||||
val summary: Int
|
||||
|
||||
@Query("UPDATE replace_rules SET isEnabled = :enable")
|
||||
fun enableAll(enable: Boolean)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg replaceRule: ReplaceRule): List<Long>
|
||||
|
||||
@Update
|
||||
fun update(vararg replaceRules: ReplaceRule)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg replaceRules: ReplaceRule)
|
||||
|
||||
private fun dealGroups(list: List<String>): List<String> {
|
||||
val groups = linkedSetOf<String>()
|
||||
list.forEach {
|
||||
it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->
|
||||
groups.add(group)
|
||||
}
|
||||
}
|
||||
return groups.sortedWith { o1, o2 ->
|
||||
o1.cnCompare(o2)
|
||||
}
|
||||
}
|
||||
|
||||
fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)
|
||||
|
||||
fun flowGroups(): Flow<List<String>> {
|
||||
return flowGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.RssArticle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface RssArticleDao {
|
||||
|
||||
@Query("select * from rssArticles where origin = :origin and link = :link")
|
||||
fun get(origin: String, link: String): RssArticle?
|
||||
|
||||
@Query(
|
||||
"""select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content,
|
||||
t1.description, t1.image, t1.`group`, t1.pubDate, t1.variable, ifNull(t2.read, 0) as read
|
||||
from rssArticles as t1 left join rssReadRecords as t2
|
||||
on t1.link = t2.record where origin = :origin and sort = :sort
|
||||
order by `order` desc"""
|
||||
)
|
||||
fun flowByOriginSort(origin: String, sort: String): Flow<List<RssArticle>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg rssArticle: RssArticle)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun append(vararg rssArticle: RssArticle)
|
||||
|
||||
@Query("delete from rssArticles where origin = :origin and sort = :sort and `order` < :order")
|
||||
fun clearOld(origin: String, sort: String, order: Long)
|
||||
|
||||
@Update
|
||||
fun update(vararg rssArticle: RssArticle)
|
||||
|
||||
@Query("update rssArticles set origin = :origin where origin = :oldOrigin")
|
||||
fun updateOrigin(origin: String, oldOrigin: String)
|
||||
|
||||
@Query("delete from rssArticles where origin = :origin")
|
||||
fun delete(origin: String)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.RssReadRecord
|
||||
|
||||
@Dao
|
||||
interface RssReadRecordDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insertRecord(vararg rssReadRecord: RssReadRecord)
|
||||
|
||||
@Query("select * from rssReadRecords order by readTime desc")
|
||||
fun getRecords(): List<RssReadRecord>
|
||||
|
||||
@get:Query("select count(1) from rssReadRecords")
|
||||
val countRecords: Int
|
||||
|
||||
@Query("delete from rssReadRecords")
|
||||
fun deleteAllRecord()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.data.entities.RssSource
|
||||
import io.legato.kazusa.utils.cnCompare
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Dao
|
||||
interface RssSourceDao {
|
||||
|
||||
@Query("select * from rssSources where sourceUrl = :key")
|
||||
fun getByKey(key: String): RssSource?
|
||||
|
||||
@Query("select * from rssSources where sourceUrl in (:sourceUrls)")
|
||||
fun getRssSources(vararg sourceUrls: String): List<RssSource>
|
||||
|
||||
@get:Query("SELECT * FROM rssSources order by customOrder")
|
||||
val all: List<RssSource>
|
||||
|
||||
@get:Query("select count(sourceUrl) from rssSources")
|
||||
val size: Int
|
||||
|
||||
@Query("SELECT * FROM rssSources order by customOrder")
|
||||
fun flowAll(): Flow<List<RssSource>>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM rssSources
|
||||
where sourceName like '%' || :key || '%'
|
||||
or sourceUrl like '%' || :key || '%'
|
||||
or sourceGroup like '%' || :key || '%'
|
||||
or sourceComment like '%' || :key || '%'
|
||||
order by customOrder"""
|
||||
)
|
||||
fun flowSearch(key: String): Flow<List<RssSource>>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM rssSources
|
||||
where (sourceGroup = :key
|
||||
or sourceGroup like :key || ',%'
|
||||
or sourceGroup like '%,' || :key
|
||||
or sourceGroup like '%,' || :key || ',%')
|
||||
order by customOrder"""
|
||||
)
|
||||
fun flowGroupSearch(key: String): Flow<List<RssSource>>
|
||||
|
||||
@Query("SELECT * FROM rssSources where enabled = 1 order by customOrder")
|
||||
fun flowEnabled(): Flow<List<RssSource>>
|
||||
|
||||
@Query("SELECT * FROM rssSources where enabled = 0 order by customOrder")
|
||||
fun flowDisabled(): Flow<List<RssSource>>
|
||||
|
||||
@Query("select * from rssSources where loginUrl is not null and loginUrl != ''")
|
||||
fun flowLogin(): Flow<List<RssSource>>
|
||||
|
||||
@Query("select * from rssSources where sourceGroup is null or sourceGroup = '' or sourceGroup like '%未分组%'")
|
||||
fun flowNoGroup(): Flow<List<RssSource>>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM rssSources
|
||||
where enabled = 1
|
||||
and (sourceName like '%' || :searchKey || '%'
|
||||
or sourceGroup like '%' || :searchKey || '%'
|
||||
or sourceUrl like '%' || :searchKey || '%'
|
||||
or sourceComment like '%' || :searchKey || '%')
|
||||
order by customOrder"""
|
||||
)
|
||||
fun flowEnabled(searchKey: String): Flow<List<RssSource>>
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM rssSources
|
||||
where enabled = 1 and (sourceGroup = :searchKey
|
||||
or sourceGroup like :searchKey || ',%'
|
||||
or sourceGroup like '%,' || :searchKey
|
||||
or sourceGroup like '%,' || :searchKey || ',%')
|
||||
order by customOrder"""
|
||||
)
|
||||
fun flowEnabledByGroup(searchKey: String): Flow<List<RssSource>>
|
||||
|
||||
@Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
|
||||
fun flowGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1")
|
||||
fun flowEnabledGroupsUnProcessed(): Flow<List<String>>
|
||||
|
||||
@get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
|
||||
val allGroupsUnProcessed: List<String>
|
||||
|
||||
@get:Query("select min(customOrder) from rssSources")
|
||||
val minOrder: Int
|
||||
|
||||
@get:Query("select max(customOrder) from rssSources")
|
||||
val maxOrder: Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg rssSource: RssSource)
|
||||
|
||||
@Update
|
||||
fun update(vararg rssSource: RssSource)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg rssSource: RssSource)
|
||||
|
||||
@Query("delete from rssSources where sourceUrl = :sourceUrl")
|
||||
fun delete(sourceUrl: String)
|
||||
|
||||
@Query("delete from rssSources where sourceGroup like 'legado'")
|
||||
fun deleteDefault()
|
||||
|
||||
@get:Query("select * from rssSources where sourceGroup is null or sourceGroup = ''")
|
||||
val noGroup: List<RssSource>
|
||||
|
||||
@Query("select * from rssSources where sourceGroup like '%' || :group || '%'")
|
||||
fun getByGroup(group: String): List<RssSource>
|
||||
|
||||
@Query("select exists(select 1 from rssSources where sourceUrl = :key)")
|
||||
fun has(key: String): Boolean
|
||||
|
||||
@Query("update rssSources set enabled = :enable where sourceUrl = :sourceUrl")
|
||||
fun enable(sourceUrl: String, enable: Boolean)
|
||||
|
||||
private fun dealGroups(list: List<String>): List<String> {
|
||||
val groups = linkedSetOf<String>()
|
||||
list.forEach {
|
||||
it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->
|
||||
groups.add(group)
|
||||
}
|
||||
}
|
||||
return groups.sortedWith { o1, o2 ->
|
||||
o1.cnCompare(o2)
|
||||
}
|
||||
}
|
||||
|
||||
fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)
|
||||
|
||||
fun flowGroups(): Flow<List<String>> {
|
||||
return flowGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
|
||||
fun flowEnabledGroups(): Flow<List<String>> {
|
||||
return flowEnabledGroupsUnProcessed().map { list ->
|
||||
dealGroups(list)
|
||||
}.flowOn(IO)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.RssStar
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface RssStarDao {
|
||||
|
||||
@get:Query("select * from rssStars order by starTime desc")
|
||||
val all: List<RssStar>
|
||||
|
||||
@Query("select `group` from rssStars group by `group` order by `group`")
|
||||
fun flowGroups(): Flow<List<String>>
|
||||
|
||||
@Query("select * from rssStars where `group` = :group order by starTime desc")
|
||||
fun flowByGroup(group: String): Flow<List<RssStar>>
|
||||
|
||||
@Query("select * from rssStars where origin = :origin and link = :link")
|
||||
fun get(origin: String, link: String): RssStar?
|
||||
|
||||
@Query("select * from rssStars order by starTime desc")
|
||||
fun liveAll(): Flow<List<RssStar>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg rssStar: RssStar)
|
||||
|
||||
@Update
|
||||
fun update(vararg rssStar: RssStar)
|
||||
|
||||
@Query("update rssStars set origin = :origin where origin = :oldOrigin")
|
||||
fun updateOrigin(origin: String, oldOrigin: String)
|
||||
|
||||
@Query("delete from rssStars where origin = :origin")
|
||||
fun delete(origin: String)
|
||||
|
||||
@Query("delete from rssStars where origin = :origin and link = :link")
|
||||
fun delete(origin: String, link: String)
|
||||
|
||||
@Query("delete from rssStars where `group` = :group")
|
||||
fun deleteByGroup(group: String)
|
||||
|
||||
@Query("delete from rssStars")
|
||||
fun deleteAll()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.RuleSub
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface RuleSubDao {
|
||||
|
||||
@get:Query("select * from ruleSubs order by customOrder")
|
||||
val all: List<RuleSub>
|
||||
|
||||
@Query("select * from ruleSubs order by customOrder")
|
||||
fun flowAll(): Flow<List<RuleSub>>
|
||||
|
||||
@get:Query("select customOrder from ruleSubs order by customOrder limit 0,1")
|
||||
val maxOrder: Int
|
||||
|
||||
@Query("select * from ruleSubs where url = :url")
|
||||
fun findByUrl(url: String): RuleSub?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg ruleSub: RuleSub)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg ruleSub: RuleSub)
|
||||
|
||||
@Update
|
||||
fun update(vararg ruleSub: RuleSub)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.SearchBook
|
||||
|
||||
@Dao
|
||||
interface SearchBookDao {
|
||||
|
||||
@Query("select * from searchBooks where bookUrl = :bookUrl")
|
||||
fun getSearchBook(bookUrl: String): SearchBook?
|
||||
|
||||
@Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources) order by originOrder limit 1")
|
||||
fun getFirstByNameAuthor(name: String, author: String): SearchBook?
|
||||
|
||||
@Query(
|
||||
"""select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl,
|
||||
t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable,
|
||||
t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount
|
||||
from searchBooks as t1 inner join book_sources as t2
|
||||
on t1.origin = t2.bookSourceUrl
|
||||
where t1.name = :name and t1.author like '%'||:author||'%'
|
||||
and t2.enabled = 1 and t2.bookSourceGroup like '%'||:sourceGroup||'%'
|
||||
order by t2.customOrder"""
|
||||
)
|
||||
fun changeSourceByGroup(name: String, author: String, sourceGroup: String): List<SearchBook>
|
||||
|
||||
@Query(
|
||||
"""select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl,
|
||||
t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable,
|
||||
t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount
|
||||
from searchBooks as t1 inner join book_sources as t2
|
||||
on t1.origin = t2.bookSourceUrl
|
||||
where t1.name = :name and t1.author like '%'||:author||'%'
|
||||
and t2.bookSourceGroup like '%'||:sourceGroup||'%'
|
||||
and (originName like '%'||:key||'%' or t1.latestChapterTitle like '%'||:key||'%')
|
||||
and t2.enabled = 1
|
||||
order by t2.customOrder"""
|
||||
)
|
||||
fun changeSourceSearch(
|
||||
name: String,
|
||||
author: String,
|
||||
key: String,
|
||||
sourceGroup: String
|
||||
): List<SearchBook>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl,
|
||||
t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable,
|
||||
t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount
|
||||
from searchBooks as t1 inner join book_sources as t2
|
||||
on t1.origin = t2.bookSourceUrl
|
||||
where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1
|
||||
order by t2.customOrder
|
||||
"""
|
||||
)
|
||||
fun getEnableHasCover(name: String, author: String): List<SearchBook>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg searchBook: SearchBook): List<Long>
|
||||
|
||||
@Query("delete from searchBooks where name = :name and author = :author")
|
||||
fun clear(name: String, author: String)
|
||||
|
||||
@Query("delete from searchBooks where time < :time")
|
||||
fun clearExpired(time: Long)
|
||||
|
||||
@Update
|
||||
fun update(vararg searchBook: SearchBook)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg searchBook: SearchBook)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.SearchKeyword
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@Dao
|
||||
interface SearchKeywordDao {
|
||||
|
||||
@get:Query("SELECT * FROM search_keywords")
|
||||
val all: List<SearchKeyword>
|
||||
|
||||
@Query("SELECT * FROM search_keywords ORDER BY usage DESC")
|
||||
fun flowByUsage(): Flow<List<SearchKeyword>>
|
||||
|
||||
@Query("SELECT * FROM search_keywords ORDER BY lastUseTime DESC")
|
||||
fun flowByTime(): Flow<List<SearchKeyword>>
|
||||
|
||||
@Query("SELECT * FROM search_keywords where word like '%'||:key||'%' ORDER BY usage DESC")
|
||||
fun flowSearch(key: String): Flow<List<SearchKeyword>>
|
||||
|
||||
@Query("select * from search_keywords where word = :key")
|
||||
fun get(key: String): SearchKeyword?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg keywords: SearchKeyword)
|
||||
|
||||
@Update
|
||||
fun update(vararg keywords: SearchKeyword)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg keywords: SearchKeyword)
|
||||
|
||||
@Query("DELETE FROM search_keywords")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.Server
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ServerDao {
|
||||
|
||||
@Query("select * from servers order by sortNumber")
|
||||
fun observeAll(): Flow<List<Server>>
|
||||
|
||||
@get:Query("select * from servers order by sortNumber")
|
||||
val all: List<Server>
|
||||
|
||||
@Query("select * from servers where id = :id")
|
||||
fun get(id: Long): Server?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg server: Server)
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun update(vararg server: Server)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg server: Server)
|
||||
|
||||
@Query("delete from servers where id = :id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Query("delete from servers where id < 0")
|
||||
fun deleteDefault()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.legato.kazusa.data.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.legato.kazusa.data.entities.TxtTocRule
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TxtTocRuleDao {
|
||||
|
||||
@Query("select * from txtTocRules order by serialNumber")
|
||||
fun observeAll(): Flow<List<TxtTocRule>>
|
||||
|
||||
@get:Query("select * from txtTocRules order by serialNumber")
|
||||
val all: List<TxtTocRule>
|
||||
|
||||
@get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
|
||||
val enabled: List<TxtTocRule>
|
||||
|
||||
@get:Query("select * from txtTocRules where enable != 1 order by serialNumber")
|
||||
val disabled: List<TxtTocRule>
|
||||
|
||||
@get:Query("select count(*) from txtTocRules")
|
||||
val count: Int
|
||||
|
||||
@Query("select * from txtTocRules where id = :id")
|
||||
fun get(id: Long): TxtTocRule?
|
||||
|
||||
@get:Query("select ifNull(min(serialNumber), 0) from txtTocRules")
|
||||
val minOrder: Int
|
||||
|
||||
@get:Query("select ifNull(max(serialNumber), 0) from txtTocRules")
|
||||
val maxOrder: Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg rule: TxtTocRule)
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun update(vararg rule: TxtTocRule)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg rule: TxtTocRule)
|
||||
|
||||
@Query("delete from txtTocRules where id < 0")
|
||||
fun deleteDefault()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import io.legato.kazusa.help.RuleBigDataHelp
|
||||
import io.legato.kazusa.model.analyzeRule.RuleDataInterface
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
|
||||
interface BaseBook : RuleDataInterface {
|
||||
var name: String
|
||||
var author: String
|
||||
var bookUrl: String
|
||||
var kind: String?
|
||||
var wordCount: String?
|
||||
var variable: String?
|
||||
|
||||
var infoHtml: String?
|
||||
var tocHtml: String?
|
||||
|
||||
override fun putVariable(key: String, value: String?): Boolean {
|
||||
if (super.putVariable(key, value)) {
|
||||
variable = GSON.toJson(variableMap)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun putCustomVariable(value: String?) {
|
||||
putVariable("custom", value)
|
||||
}
|
||||
|
||||
fun getCustomVariable(): String {
|
||||
return getVariable("custom")
|
||||
}
|
||||
|
||||
override fun putBigVariable(key: String, value: String?) {
|
||||
RuleBigDataHelp.putBookVariable(bookUrl, key, value)
|
||||
}
|
||||
|
||||
override fun getBigVariable(key: String): String? {
|
||||
return RuleBigDataHelp.getBookVariable(bookUrl, key)
|
||||
}
|
||||
|
||||
fun getKindList(): List<String> {
|
||||
val kindList = arrayListOf<String>()
|
||||
wordCount?.let {
|
||||
if (it.isNotBlank()) kindList.add(it)
|
||||
}
|
||||
kind?.let {
|
||||
val kinds = it.splitNotBlank(",", "\n")
|
||||
kindList.addAll(kinds)
|
||||
}
|
||||
return kindList
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import io.legato.kazusa.help.RuleBigDataHelp
|
||||
import io.legato.kazusa.model.analyzeRule.RuleDataInterface
|
||||
import io.legato.kazusa.utils.GSON
|
||||
|
||||
interface BaseRssArticle : RuleDataInterface {
|
||||
|
||||
var origin: String
|
||||
var link: String
|
||||
|
||||
var variable: String?
|
||||
|
||||
override fun putVariable(key: String, value: String?): Boolean {
|
||||
if (super.putVariable(key, value)) {
|
||||
variable = GSON.toJson(variableMap)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun putBigVariable(key: String, value: String?) {
|
||||
RuleBigDataHelp.putRssVariable(origin, link, key, value)
|
||||
}
|
||||
|
||||
override fun getBigVariable(key: String): String? {
|
||||
return RuleBigDataHelp.getRssVariable(origin, link, key)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import cn.hutool.crypto.symmetric.AES
|
||||
import com.script.ScriptBindings
|
||||
import com.script.buildScriptBindings
|
||||
import com.script.rhino.RhinoScriptEngine
|
||||
import io.legato.kazusa.constant.AppConst
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.data.entities.rule.RowUi
|
||||
import io.legato.kazusa.help.CacheManager
|
||||
import io.legato.kazusa.help.JsExtensions
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.crypto.SymmetricCryptoAndroid
|
||||
import io.legato.kazusa.help.http.CookieStore
|
||||
import io.legato.kazusa.help.source.getShareScope
|
||||
import io.legato.kazusa.model.Debug
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.GSONStrict
|
||||
import io.legato.kazusa.utils.fromJsonArray
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import io.legato.kazusa.utils.has
|
||||
import io.legato.kazusa.utils.printOnDebug
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
/**
|
||||
* 可在js里调用,source.xxx()
|
||||
*/
|
||||
@Suppress("unused")
|
||||
interface BaseSource : JsExtensions {
|
||||
/**
|
||||
* 并发率
|
||||
*/
|
||||
var concurrentRate: String?
|
||||
|
||||
/**
|
||||
* 登录地址
|
||||
*/
|
||||
var loginUrl: String?
|
||||
|
||||
/**
|
||||
* 登录UI
|
||||
*/
|
||||
var loginUi: String?
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
var header: String?
|
||||
|
||||
/**
|
||||
* 启用cookieJar
|
||||
*/
|
||||
var enabledCookieJar: Boolean?
|
||||
|
||||
/**
|
||||
* js库
|
||||
*/
|
||||
var jsLib: String?
|
||||
|
||||
fun getTag(): String
|
||||
|
||||
fun getKey(): String
|
||||
|
||||
override fun getSource(): BaseSource? {
|
||||
return this
|
||||
}
|
||||
|
||||
fun loginUi(): List<RowUi>? {
|
||||
return GSON.fromJsonArray<RowUi>(loginUi).onFailure {
|
||||
it.printOnDebug()
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
fun getLoginJs(): String? {
|
||||
val loginJs = loginUrl
|
||||
return when {
|
||||
loginJs == null -> null
|
||||
loginJs.startsWith("@js:") -> loginJs.substring(4)
|
||||
loginJs.startsWith("<js>") -> loginJs.substring(4, loginJs.lastIndexOf("<"))
|
||||
else -> loginJs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用login函数 实现登录请求
|
||||
*/
|
||||
fun login() {
|
||||
val loginJs = getLoginJs()
|
||||
if (!loginJs.isNullOrBlank()) {
|
||||
@Language("js")
|
||||
val js = """$loginJs
|
||||
if(typeof login=='function'){
|
||||
login.apply(this);
|
||||
} else {
|
||||
throw('Function login not implements!!!')
|
||||
}
|
||||
""".trimIndent()
|
||||
evalJS(js)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析header规则
|
||||
*/
|
||||
fun getHeaderMap(hasLoginHeader: Boolean = false) = HashMap<String, String>().apply {
|
||||
header?.let {
|
||||
try {
|
||||
val json = when {
|
||||
it.startsWith("@js:", true) -> evalJS(it.substring(4)).toString()
|
||||
it.startsWith("<js>", true) -> evalJS(
|
||||
it.substring(4, it.lastIndexOf("<"))
|
||||
).toString()
|
||||
|
||||
else -> it
|
||||
}
|
||||
GSONStrict.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
||||
putAll(map)
|
||||
} ?: GSON.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->
|
||||
Debug.log("≡请求头规则 JSON 格式不规范,请改为规范格式")
|
||||
putAll(map)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("执行请求头规则出错\n$e", e)
|
||||
}
|
||||
}
|
||||
if (!has(AppConst.UA_NAME, true)) {
|
||||
put(AppConst.UA_NAME, AppConfig.userAgent)
|
||||
}
|
||||
if (hasLoginHeader) {
|
||||
getLoginHeaderMap()?.let {
|
||||
putAll(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用于登录的头部信息
|
||||
*/
|
||||
fun getLoginHeader(): String? {
|
||||
return CacheManager.get("loginHeader_${getKey()}")
|
||||
}
|
||||
|
||||
fun getLoginHeaderMap(): Map<String, String>? {
|
||||
val cache = getLoginHeader() ?: return null
|
||||
return GSON.fromJsonObject<Map<String, String>>(cache).getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存登录头部信息,map格式,访问时自动添加
|
||||
*/
|
||||
fun putLoginHeader(header: String) {
|
||||
val headerMap = GSON.fromJsonObject<Map<String, String>>(header).getOrNull()
|
||||
val cookie = headerMap?.get("Cookie") ?: headerMap?.get("cookie")
|
||||
cookie?.let {
|
||||
CookieStore.replaceCookie(getKey(), it)
|
||||
}
|
||||
CacheManager.put("loginHeader_${getKey()}", header)
|
||||
}
|
||||
|
||||
fun removeLoginHeader() {
|
||||
CacheManager.delete("loginHeader_${getKey()}")
|
||||
CookieStore.removeCookie(getKey())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息,可以用来登录
|
||||
* 用户信息采用aes加密存储
|
||||
*/
|
||||
fun getLoginInfo(): String? {
|
||||
try {
|
||||
val key = AppConst.androidId.encodeToByteArray(0, 16)
|
||||
val cache = CacheManager.get("userInfo_${getKey()}") ?: return null
|
||||
return AES(key).decryptStr(cache)
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("获取登陆信息出错", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getLoginInfoMap(): Map<String, String>? {
|
||||
return GSON.fromJsonObject<Map<String, String>>(getLoginInfo()).getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户信息,aes加密
|
||||
*/
|
||||
fun putLoginInfo(info: String): Boolean {
|
||||
return try {
|
||||
val key = (AppConst.androidId).encodeToByteArray(0, 16)
|
||||
val encodeStr = SymmetricCryptoAndroid("AES", key).encryptBase64(info)
|
||||
CacheManager.put("userInfo_${getKey()}", encodeStr)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("保存登陆信息出错", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun removeLoginInfo() {
|
||||
CacheManager.delete("userInfo_${getKey()}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义变量
|
||||
* @param variable 变量内容
|
||||
*/
|
||||
fun setVariable(variable: String?) {
|
||||
if (variable != null) {
|
||||
CacheManager.put("sourceVariable_${getKey()}", variable)
|
||||
} else {
|
||||
CacheManager.delete("sourceVariable_${getKey()}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义变量
|
||||
*/
|
||||
fun getVariable(): String {
|
||||
return CacheManager.get("sourceVariable_${getKey()}") ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*/
|
||||
fun put(key: String, value: String): String {
|
||||
CacheManager.put("v_${getKey()}_${key}", value)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存的数据
|
||||
*/
|
||||
fun get(key: String): String {
|
||||
return CacheManager.get("v_${getKey()}_${key}") ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行JS
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {
|
||||
val bindings = buildScriptBindings { bindings ->
|
||||
bindings["java"] = this
|
||||
bindings["source"] = this
|
||||
bindings["baseUrl"] = getKey()
|
||||
bindings["cookie"] = CookieStore
|
||||
bindings["cache"] = CacheManager
|
||||
bindings.apply(bindingsConfig)
|
||||
}
|
||||
val sharedScope = getShareScope()
|
||||
val scope = if (sharedScope == null) {
|
||||
RhinoScriptEngine.getRuntimeScope(bindings)
|
||||
} else {
|
||||
bindings.apply {
|
||||
prototype = sharedScope
|
||||
}
|
||||
}
|
||||
return RhinoScriptEngine.eval(jsStr, scope)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.constant.PageAnim
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.help.book.BookHelp
|
||||
import io.legato.kazusa.help.book.ContentProcessor
|
||||
import io.legato.kazusa.help.book.getFolderNameNoCache
|
||||
import io.legato.kazusa.help.book.isEpub
|
||||
import io.legato.kazusa.help.book.isImage
|
||||
import io.legato.kazusa.help.book.simulatedTotalChapterNum
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.config.ReadBookConfig
|
||||
import io.legato.kazusa.model.ReadBook
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.charset.Charset
|
||||
import java.time.LocalDate
|
||||
import kotlin.math.max
|
||||
|
||||
@Parcelize
|
||||
@TypeConverters(Book.Converters::class)
|
||||
@Entity(
|
||||
tableName = "books",
|
||||
indices = [Index(value = ["name", "author"], unique = true)]
|
||||
)
|
||||
data class Book(
|
||||
// 详情页Url(本地书源存储完整文件路径)
|
||||
@PrimaryKey
|
||||
@ColumnInfo(defaultValue = "")
|
||||
override var bookUrl: String = "",
|
||||
// 目录页Url (toc=table of Contents)
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var tocUrl: String = "",
|
||||
// 书源URL(默认BookType.local)
|
||||
@ColumnInfo(defaultValue = BookType.localTag)
|
||||
var origin: String = BookType.localTag,
|
||||
//书源名称 or 本地书籍文件名
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var originName: String = "",
|
||||
// 书籍名称(书源获取)
|
||||
@ColumnInfo(defaultValue = "")
|
||||
override var name: String = "",
|
||||
// 作者名称(书源获取)
|
||||
@ColumnInfo(defaultValue = "")
|
||||
override var author: String = "",
|
||||
// 分类信息(书源获取)
|
||||
override var kind: String? = null,
|
||||
// 分类信息(用户修改)
|
||||
var customTag: String? = null,
|
||||
// 封面Url(书源获取)
|
||||
var coverUrl: String? = null,
|
||||
// 封面Url(用户修改)
|
||||
var customCoverUrl: String? = null,
|
||||
// 简介内容(书源获取)
|
||||
var intro: String? = null,
|
||||
// 简介内容(用户修改)
|
||||
var customIntro: String? = null,
|
||||
// 自定义字符集名称(仅适用于本地书籍)
|
||||
var charset: String? = null,
|
||||
// 类型,详见BookType
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var type: Int = BookType.text,
|
||||
// 自定义分组索引号
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var group: Long = 0,
|
||||
// 最新章节标题
|
||||
var latestChapterTitle: String? = null,
|
||||
// 最新章节标题更新时间
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var latestChapterTime: Long = System.currentTimeMillis(),
|
||||
// 最近一次更新书籍信息的时间
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastCheckTime: Long = System.currentTimeMillis(),
|
||||
// 最近一次发现新章节的数量
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastCheckCount: Int = 0,
|
||||
// 书籍目录总数
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var totalChapterNum: Int = 0,
|
||||
// 当前章节名称
|
||||
var durChapterTitle: String? = null,
|
||||
// 当前章节索引
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var durChapterIndex: Int = 0,
|
||||
// 当前阅读的进度(首行字符的索引位置)
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var durChapterPos: Int = 0,
|
||||
// 最近一次阅读书籍的时间(打开正文的时间)
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var durChapterTime: Long = System.currentTimeMillis(),
|
||||
//字数
|
||||
override var wordCount: String? = null,
|
||||
// 刷新书架时更新书籍信息
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var canUpdate: Boolean = true,
|
||||
// 手动排序
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var order: Int = 0,
|
||||
//书源排序
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var originOrder: Int = 0,
|
||||
// 自定义书籍变量信息(用于书源规则检索书籍信息)
|
||||
override var variable: String? = null,
|
||||
//阅读设置
|
||||
var readConfig: ReadConfig? = null,
|
||||
//同步时间
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var syncTime: Long = 0L
|
||||
) : Parcelable, BaseBook {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is Book) {
|
||||
return other.bookUrl == bookUrl
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return bookUrl.hashCode()
|
||||
}
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
override val variableMap: HashMap<String, String> by lazy {
|
||||
GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
override var infoHtml: String? = null
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
override var tocHtml: String? = null
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
var downloadUrls: List<String>? = null
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
private var folderName: String? = null
|
||||
|
||||
@get:Ignore
|
||||
@IgnoredOnParcel
|
||||
val lastChapterIndex get() = totalChapterNum - 1
|
||||
|
||||
fun getRealAuthor() = author.replace(AppPattern.authorRegex, "")
|
||||
|
||||
fun getUnreadChapterNum() = max(simulatedTotalChapterNum() - durChapterIndex - 1, 0)
|
||||
|
||||
fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
|
||||
|
||||
fun getDisplayIntro() = if (customIntro.isNullOrEmpty()) intro else customIntro
|
||||
|
||||
//自定义简介有自动更新的需求时,可通过更新intro再调用upCustomIntro()完成
|
||||
@Suppress("unused")
|
||||
fun upCustomIntro() {
|
||||
customIntro = intro
|
||||
}
|
||||
|
||||
fun fileCharset(): Charset {
|
||||
return charset(charset ?: "UTF-8")
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
val config: ReadConfig
|
||||
get() {
|
||||
if (readConfig == null) {
|
||||
readConfig = ReadConfig()
|
||||
}
|
||||
return readConfig!!
|
||||
}
|
||||
|
||||
fun setReverseToc(reverseToc: Boolean) {
|
||||
config.reverseToc = reverseToc
|
||||
}
|
||||
|
||||
fun getReverseToc(): Boolean {
|
||||
return config.reverseToc
|
||||
}
|
||||
|
||||
fun setUseReplaceRule(useReplaceRule: Boolean) {
|
||||
config.useReplaceRule = useReplaceRule
|
||||
}
|
||||
|
||||
fun getUseReplaceRule(): Boolean {
|
||||
val useReplaceRule = config.useReplaceRule
|
||||
if (useReplaceRule != null) {
|
||||
return useReplaceRule
|
||||
}
|
||||
//图片类书源 epub本地 默认关闭净化
|
||||
if (isImage || isEpub) {
|
||||
return false
|
||||
}
|
||||
return AppConfig.replaceEnableDefault
|
||||
}
|
||||
|
||||
fun setReSegment(reSegment: Boolean) {
|
||||
config.reSegment = reSegment
|
||||
}
|
||||
|
||||
fun getReSegment(): Boolean {
|
||||
return config.reSegment
|
||||
}
|
||||
|
||||
fun setPageAnim(pageAnim: Int?) {
|
||||
config.pageAnim = pageAnim
|
||||
}
|
||||
|
||||
fun getPageAnim(): Int {
|
||||
var pageAnim = config.pageAnim
|
||||
?: if (type and BookType.image > 0) PageAnim.scrollPageAnim else ReadBookConfig.pageAnim
|
||||
if (pageAnim < 0) {
|
||||
pageAnim = ReadBookConfig.pageAnim
|
||||
}
|
||||
return pageAnim
|
||||
}
|
||||
|
||||
fun setImageStyle(imageStyle: String?) {
|
||||
config.imageStyle = imageStyle
|
||||
}
|
||||
|
||||
fun getImageStyle(): String? {
|
||||
return config.imageStyle
|
||||
}
|
||||
|
||||
fun setTtsEngine(ttsEngine: String?) {
|
||||
config.ttsEngine = ttsEngine
|
||||
}
|
||||
|
||||
fun getTtsEngine(): String? {
|
||||
return config.ttsEngine
|
||||
}
|
||||
|
||||
fun setSplitLongChapter(limitLongContent: Boolean) {
|
||||
config.splitLongChapter = limitLongContent
|
||||
}
|
||||
|
||||
fun getSplitLongChapter(): Boolean {
|
||||
return config.splitLongChapter
|
||||
}
|
||||
|
||||
// readSimulating 的 setter 和 getter
|
||||
fun setReadSimulating(readSimulating: Boolean) {
|
||||
config.readSimulating = readSimulating
|
||||
}
|
||||
|
||||
fun getReadSimulating(): Boolean {
|
||||
return config.readSimulating
|
||||
}
|
||||
|
||||
// startDate 的 setter 和 getter
|
||||
fun setStartDate(startDate: LocalDate?) {
|
||||
config.startDate = startDate
|
||||
}
|
||||
|
||||
fun getStartDate(): LocalDate? {
|
||||
if (!config.readSimulating || config.startDate == null) {
|
||||
return LocalDate.now()
|
||||
}
|
||||
return config.startDate
|
||||
}
|
||||
|
||||
// startChapter 的 setter 和 getter
|
||||
fun setStartChapter(startChapter: Int) {
|
||||
config.startChapter = startChapter
|
||||
}
|
||||
|
||||
fun getStartChapter(): Int {
|
||||
if (config.readSimulating) return config.startChapter ?: 0
|
||||
return this.durChapterIndex
|
||||
}
|
||||
|
||||
// dailyChapters 的 setter 和 getter
|
||||
fun setDailyChapters(dailyChapters: Int) {
|
||||
config.dailyChapters = dailyChapters
|
||||
}
|
||||
|
||||
fun getDailyChapters(): Int {
|
||||
return config.dailyChapters
|
||||
}
|
||||
|
||||
fun getDelTag(tag: Long): Boolean {
|
||||
return config.delTag and tag == tag
|
||||
}
|
||||
|
||||
fun addDelTag(tag: Long) {
|
||||
config.delTag = config.delTag and tag
|
||||
}
|
||||
|
||||
fun removeDelTag(tag: Long) {
|
||||
config.delTag = config.delTag and tag.inv()
|
||||
}
|
||||
|
||||
fun getFolderName(): String {
|
||||
folderName?.let {
|
||||
return it
|
||||
}
|
||||
//防止书名过长,只取9位
|
||||
folderName = getFolderNameNoCache()
|
||||
return folderName!!
|
||||
}
|
||||
|
||||
fun toSearchBook() = SearchBook(
|
||||
name = name,
|
||||
author = author,
|
||||
kind = kind,
|
||||
bookUrl = bookUrl,
|
||||
origin = origin,
|
||||
originName = originName,
|
||||
type = type,
|
||||
wordCount = wordCount,
|
||||
latestChapterTitle = latestChapterTitle,
|
||||
coverUrl = coverUrl,
|
||||
intro = intro,
|
||||
tocUrl = tocUrl,
|
||||
originOrder = originOrder,
|
||||
variable = variable
|
||||
).apply {
|
||||
this.infoHtml = this@Book.infoHtml
|
||||
this.tocHtml = this@Book.tocHtml
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移旧的书籍的一些信息到新的书籍中
|
||||
*/
|
||||
fun migrateTo(newBook: Book, toc: List<BookChapter>): Book {
|
||||
newBook.durChapterIndex = BookHelp
|
||||
.getDurChapter(durChapterIndex, durChapterTitle, toc, totalChapterNum)
|
||||
newBook.durChapterTitle = toc[newBook.durChapterIndex].getDisplayTitle(
|
||||
ContentProcessor.get(newBook.name, newBook.origin).getTitleReplaceRules(),
|
||||
getUseReplaceRule()
|
||||
)
|
||||
newBook.durChapterPos = durChapterPos
|
||||
newBook.durChapterTime = durChapterTime
|
||||
newBook.group = group
|
||||
newBook.order = order
|
||||
newBook.customCoverUrl = customCoverUrl
|
||||
newBook.customIntro = customIntro
|
||||
newBook.customTag = customTag
|
||||
newBook.canUpdate = canUpdate
|
||||
newBook.readConfig = readConfig
|
||||
return newBook
|
||||
}
|
||||
|
||||
fun createBookMark(): Bookmark {
|
||||
return Bookmark(
|
||||
bookName = name,
|
||||
bookAuthor = author,
|
||||
)
|
||||
}
|
||||
|
||||
fun save() {
|
||||
if (appDb.bookDao.has(bookUrl)) {
|
||||
appDb.bookDao.update(this)
|
||||
} else {
|
||||
appDb.bookDao.insert(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete() {
|
||||
if (ReadBook.book?.bookUrl == bookUrl) {
|
||||
ReadBook.book = null
|
||||
}
|
||||
appDb.bookDao.delete(this)
|
||||
}
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
companion object {
|
||||
const val hTag = 2L
|
||||
const val rubyTag = 4L
|
||||
const val imgStyleDefault = "DEFAULT"
|
||||
const val imgStyleFull = "FULL"
|
||||
const val imgStyleText = "TEXT"
|
||||
const val imgStyleSingle = "SINGLE"
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class ReadConfig(
|
||||
var reverseToc: Boolean = false,
|
||||
var pageAnim: Int? = null,
|
||||
var reSegment: Boolean = false,
|
||||
var imageStyle: String? = null,
|
||||
var useReplaceRule: Boolean? = null,// 正文使用净化替换规则
|
||||
var delTag: Long = 0L,//去除标签
|
||||
var ttsEngine: String? = null,
|
||||
var splitLongChapter: Boolean = true,
|
||||
var readSimulating: Boolean = false,
|
||||
var startDate: LocalDate? = null,
|
||||
var startChapter: Int? = null, // 用户设置的起始章节
|
||||
var dailyChapters: Int = 3 // 用户设置的每日更新章节数
|
||||
) : Parcelable
|
||||
|
||||
class Converters {
|
||||
|
||||
@TypeConverter
|
||||
fun readConfigToString(config: ReadConfig?): String = GSON.toJson(config)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json).getOrNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.exception.RegexTimeoutException
|
||||
import io.legato.kazusa.help.RuleBigDataHelp
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.model.analyzeRule.AnalyzeUrl
|
||||
import io.legato.kazusa.model.analyzeRule.RuleDataInterface
|
||||
import io.legato.kazusa.utils.ChineseUtils
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.MD5Utils
|
||||
import io.legato.kazusa.utils.NetworkUtils
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import io.legato.kazusa.utils.replace
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import splitties.init.appCtx
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "chapters",
|
||||
primaryKeys = ["url", "bookUrl"],
|
||||
indices = [(Index(value = ["bookUrl"], unique = false)),
|
||||
(Index(value = ["bookUrl", "index"], unique = true))],
|
||||
foreignKeys = [(ForeignKey(
|
||||
entity = Book::class,
|
||||
parentColumns = ["bookUrl"],
|
||||
childColumns = ["bookUrl"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
))]
|
||||
) // 删除书籍时自动删除章节
|
||||
data class BookChapter(
|
||||
var url: String = "", // 章节地址
|
||||
var title: String = "", // 章节标题
|
||||
var isVolume: Boolean = false, // 是否是卷名
|
||||
var baseUrl: String = "", // 用来拼接相对url
|
||||
var bookUrl: String = "", // 书籍地址
|
||||
var index: Int = 0, // 章节序号
|
||||
var isVip: Boolean = false, // 是否VIP
|
||||
var isPay: Boolean = false, // 是否已购买
|
||||
var resourceUrl: String? = null, // 音频真实URL
|
||||
var tag: String? = null, // 更新时间或其他章节附加信息
|
||||
var wordCount: String? = null, // 本章节字数
|
||||
var start: Long? = null, // 章节起始位置
|
||||
var end: Long? = null, // 章节终止位置
|
||||
var startFragmentId: String? = null, //EPUB书籍当前章节的fragmentId
|
||||
var endFragmentId: String? = null, //EPUB书籍下一章节的fragmentId
|
||||
var variable: String? = null //变量
|
||||
) : Parcelable, RuleDataInterface {
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
override val variableMap: HashMap<String, String> by lazy {
|
||||
GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
var titleMD5: String? = null
|
||||
|
||||
override fun putVariable(key: String, value: String?): Boolean {
|
||||
if (super.putVariable(key, value)) {
|
||||
variable = GSON.toJson(variableMap)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun putBigVariable(key: String, value: String?) {
|
||||
RuleBigDataHelp.putChapterVariable(bookUrl, url, key, value)
|
||||
}
|
||||
|
||||
override fun getBigVariable(key: String): String? {
|
||||
return RuleBigDataHelp.getChapterVariable(bookUrl, url, key)
|
||||
}
|
||||
|
||||
override fun hashCode() = url.hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is BookChapter) {
|
||||
return other.url == url
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun primaryStr(): String {
|
||||
return bookUrl + url
|
||||
}
|
||||
|
||||
fun getDisplayTitle(
|
||||
replaceRules: List<ReplaceRule>? = null,
|
||||
useReplace: Boolean = true,
|
||||
chineseConvert: Boolean = true,
|
||||
): String {
|
||||
var displayTitle = title.replace(AppPattern.rnRegex, "")
|
||||
if (chineseConvert) {
|
||||
when (AppConfig.chineseConverterType) {
|
||||
1 -> displayTitle = ChineseUtils.t2s(displayTitle)
|
||||
2 -> displayTitle = ChineseUtils.s2t(displayTitle)
|
||||
}
|
||||
}
|
||||
if (useReplace && replaceRules != null) kotlin.run {
|
||||
replaceRules.forEach { item ->
|
||||
if (item.pattern.isNotEmpty()) {
|
||||
try {
|
||||
val mDisplayTitle = if (item.isRegex) {
|
||||
displayTitle.replace(
|
||||
item.regex,
|
||||
item.replacement,
|
||||
item.getValidTimeoutMillisecond()
|
||||
)
|
||||
} else {
|
||||
displayTitle.replace(item.pattern, item.replacement)
|
||||
}
|
||||
if (mDisplayTitle.isNotBlank()) {
|
||||
displayTitle = mDisplayTitle
|
||||
}
|
||||
} catch (e: RegexTimeoutException) {
|
||||
item.isEnabled = false
|
||||
appDb.replaceRuleDao.update(item)
|
||||
} catch (e: CancellationException) {
|
||||
return@run
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("${item.name}替换出错\n替换内容\n${displayTitle}", e)
|
||||
appCtx.toastOnUi("${item.name}替换出错")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return displayTitle
|
||||
}
|
||||
|
||||
fun getAbsoluteURL(): String {
|
||||
//二级目录解析的卷链接为空 返回目录页的链接
|
||||
if (url.startsWith(title) && isVolume) return baseUrl
|
||||
val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)
|
||||
val urlBefore = if (urlMatcher.find()) url.substring(0, urlMatcher.start()) else url
|
||||
val urlAbsoluteBefore = NetworkUtils.getAbsoluteURL(baseUrl, urlBefore)
|
||||
return if (urlBefore.length == url.length) {
|
||||
urlAbsoluteBefore
|
||||
} else {
|
||||
"$urlAbsoluteBefore," + url.substring(urlMatcher.end())
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureTitleMD5Init() {
|
||||
if (titleMD5 == null) {
|
||||
titleMD5 = MD5Utils.md5Encode16(title)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Suppress("unused")
|
||||
fun getFileName(suffix: String = "nb"): String {
|
||||
ensureTitleMD5Init()
|
||||
return String.format("%05d-%s.%s", index, titleMD5, suffix)
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Suppress("unused")
|
||||
fun getFontName(): String {
|
||||
ensureTitleMD5Init()
|
||||
return String.format("%05d-%s.ttf", index, titleMD5)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class BookChapterReview(
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var bookId: Long = 0,
|
||||
var chapterId: Long = 0,
|
||||
var summaryUrl: String = "",
|
||||
): Parcelable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
@Parcelize
|
||||
@Entity(tableName = "book_groups")
|
||||
data class BookGroup(
|
||||
@PrimaryKey
|
||||
val groupId: Long = 0b1,
|
||||
var groupName: String = "",
|
||||
var cover: String? = null,
|
||||
var order: Int = 0,
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var enableRefresh: Boolean = true,
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var show: Boolean = true,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
var bookSort: Int = -1
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
const val IdRoot = -100L
|
||||
const val IdAll = -1L
|
||||
const val IdLocal = -2L
|
||||
const val IdAudio = -3L
|
||||
const val IdNetNone = -4L
|
||||
const val IdLocalNone = -5L
|
||||
const val IdError = -11L
|
||||
}
|
||||
|
||||
fun getManageName(context: Context): String {
|
||||
return when (groupId) {
|
||||
IdAll -> "$groupName(${context.getString(R.string.all)})"
|
||||
IdAudio -> "$groupName(${context.getString(R.string.audio)})"
|
||||
IdLocal -> "$groupName(${context.getString(R.string.local)})"
|
||||
IdNetNone -> "$groupName(${context.getString(R.string.net_no_group)})"
|
||||
IdLocalNone -> "$groupName(${context.getString(R.string.local_no_group)})"
|
||||
IdError -> "$groupName(${context.getString(R.string.update_book_fail)})"
|
||||
else -> groupName
|
||||
}
|
||||
}
|
||||
|
||||
fun getRealBookSort(): Int {
|
||||
if (bookSort < 0) {
|
||||
return AppConfig.bookshelfSort
|
||||
}
|
||||
return bookSort
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return groupId.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is BookGroup) {
|
||||
return other.groupId == groupId
|
||||
&& other.groupName == groupName
|
||||
&& other.cover == cover
|
||||
&& other.bookSort == bookSort
|
||||
&& other.enableRefresh == enableRefresh
|
||||
&& other.show == show
|
||||
&& other.order == order
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
data class BookProgress(
|
||||
val name: String,
|
||||
val author: String,
|
||||
val durChapterIndex: Int,
|
||||
val durChapterPos: Int,
|
||||
val durChapterTime: Long,
|
||||
val durChapterTitle: String?
|
||||
) {
|
||||
|
||||
constructor(book: Book) : this(
|
||||
name = book.name,
|
||||
author = book.author,
|
||||
durChapterIndex = book.durChapterIndex,
|
||||
durChapterPos = book.durChapterPos,
|
||||
durChapterTime = book.durChapterTime,
|
||||
durChapterTitle = book.durChapterTitle
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.constant.BookSourceType
|
||||
import io.legato.kazusa.data.entities.rule.BookInfoRule
|
||||
import io.legato.kazusa.data.entities.rule.ContentRule
|
||||
import io.legato.kazusa.data.entities.rule.ExploreRule
|
||||
import io.legato.kazusa.data.entities.rule.ReviewRule
|
||||
import io.legato.kazusa.data.entities.rule.SearchRule
|
||||
import io.legato.kazusa.data.entities.rule.TocRule
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Suppress("unused")
|
||||
@Parcelize
|
||||
@TypeConverters(BookSource.Converters::class)
|
||||
@Entity(
|
||||
tableName = "book_sources",
|
||||
indices = [(Index(value = ["bookSourceUrl"], unique = false))]
|
||||
)
|
||||
data class BookSource(
|
||||
// 地址,包括 http/https
|
||||
@PrimaryKey
|
||||
var bookSourceUrl: String = "",
|
||||
// 名称
|
||||
var bookSourceName: String = "",
|
||||
// 分组
|
||||
var bookSourceGroup: String? = null,
|
||||
// 类型,0 文本,1 音频, 2 图片, 3 文件(指的是类似知轩藏书只提供下载的网站)
|
||||
@BookSourceType.Type
|
||||
var bookSourceType: Int = 0,
|
||||
// 详情页url正则
|
||||
var bookUrlPattern: String? = null,
|
||||
// 手动排序编号
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var customOrder: Int = 0,
|
||||
// 是否启用
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var enabled: Boolean = true,
|
||||
// 启用发现
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var enabledExplore: Boolean = true,
|
||||
// js库
|
||||
override var jsLib: String? = null,
|
||||
// 启用okhttp CookieJAr 自动保存每次请求的cookie
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
override var enabledCookieJar: Boolean? = true,
|
||||
// 并发率
|
||||
override var concurrentRate: String? = null,
|
||||
// 请求头
|
||||
override var header: String? = null,
|
||||
// 登录地址
|
||||
override var loginUrl: String? = null,
|
||||
// 登录UI
|
||||
override var loginUi: String? = null,
|
||||
// 登录检测js
|
||||
var loginCheckJs: String? = null,
|
||||
// 封面解密js
|
||||
var coverDecodeJs: String? = null,
|
||||
// 注释
|
||||
var bookSourceComment: String? = null,
|
||||
// 自定义变量说明
|
||||
var variableComment: String? = null,
|
||||
// 最后更新时间,用于排序
|
||||
var lastUpdateTime: Long = 0,
|
||||
// 响应时间,用于排序
|
||||
var respondTime: Long = 180000L,
|
||||
// 智能排序的权重
|
||||
var weight: Int = 0,
|
||||
// 发现url
|
||||
var exploreUrl: String? = null,
|
||||
// 发现筛选规则
|
||||
var exploreScreen: String? = null,
|
||||
// 发现规则
|
||||
var ruleExplore: ExploreRule? = null,
|
||||
// 搜索url
|
||||
var searchUrl: String? = null,
|
||||
// 搜索规则
|
||||
var ruleSearch: SearchRule? = null,
|
||||
// 书籍信息页规则
|
||||
var ruleBookInfo: BookInfoRule? = null,
|
||||
// 目录页规则
|
||||
var ruleToc: TocRule? = null,
|
||||
// 正文页规则
|
||||
var ruleContent: ContentRule? = null,
|
||||
// 段评规则
|
||||
var ruleReview: ReviewRule? = null
|
||||
) : Parcelable, BaseSource {
|
||||
|
||||
override fun getTag(): String {
|
||||
return bookSourceName
|
||||
}
|
||||
|
||||
override fun getKey(): String {
|
||||
return bookSourceUrl
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return bookSourceUrl.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is BookSource) other.bookSourceUrl == bookSourceUrl else false
|
||||
}
|
||||
|
||||
fun getSearchRule(): SearchRule {
|
||||
ruleSearch?.let { return it }
|
||||
val rule = SearchRule()
|
||||
ruleSearch = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getExploreRule(): ExploreRule {
|
||||
ruleExplore?.let { return it }
|
||||
val rule = ExploreRule()
|
||||
ruleExplore = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getBookInfoRule(): BookInfoRule {
|
||||
ruleBookInfo?.let { return it }
|
||||
val rule = BookInfoRule()
|
||||
ruleBookInfo = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getTocRule(): TocRule {
|
||||
ruleToc?.let { return it }
|
||||
val rule = TocRule()
|
||||
ruleToc = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
fun getContentRule(): ContentRule {
|
||||
ruleContent?.let { return it }
|
||||
val rule = ContentRule()
|
||||
ruleContent = rule
|
||||
return rule
|
||||
}
|
||||
|
||||
// fun getReviewRule(): ReviewRule {
|
||||
// ruleReview?.let { return it }
|
||||
// val rule = ReviewRule()
|
||||
// ruleReview = rule
|
||||
// return rule
|
||||
// }
|
||||
|
||||
fun getDisPlayNameGroup(): String {
|
||||
return if (bookSourceGroup.isNullOrBlank()) {
|
||||
bookSourceName
|
||||
} else {
|
||||
String.format("%s (%s)", bookSourceName, bookSourceGroup)
|
||||
}
|
||||
}
|
||||
|
||||
fun addGroup(groups: String): BookSource {
|
||||
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))
|
||||
bookSourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
if (bookSourceGroup.isNullOrBlank()) bookSourceGroup = groups
|
||||
return this
|
||||
}
|
||||
|
||||
fun removeGroup(groups: String): BookSource {
|
||||
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())
|
||||
bookSourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun hasGroup(group: String): Boolean {
|
||||
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
return it.indexOf(group) != -1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun removeInvalidGroups() {
|
||||
removeGroup(getInvalidGroupNames())
|
||||
}
|
||||
|
||||
fun removeErrorComment() {
|
||||
bookSourceComment = bookSourceComment
|
||||
?.split("\n\n")
|
||||
?.filterNot {
|
||||
it.startsWith("// Error: ")
|
||||
}?.joinToString("\n")
|
||||
}
|
||||
|
||||
fun addErrorComment(e: Throwable) {
|
||||
bookSourceComment =
|
||||
"// Error: ${e.localizedMessage}" + if (bookSourceComment.isNullOrBlank())
|
||||
"" else "\n\n${bookSourceComment}"
|
||||
}
|
||||
|
||||
fun getCheckKeyword(default: String): String {
|
||||
ruleSearch?.checkKeyWord?.let {
|
||||
if (it.isNotBlank()) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return default
|
||||
}
|
||||
|
||||
fun getInvalidGroupNames(): String {
|
||||
return bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.filter {
|
||||
"失效" in it || it == "校验超时"
|
||||
}?.joinToString() ?: ""
|
||||
}
|
||||
|
||||
fun getDisplayVariableComment(otherComment: String): String {
|
||||
return if (variableComment.isNullOrBlank()) {
|
||||
otherComment
|
||||
} else {
|
||||
"${variableComment}\n$otherComment"
|
||||
}
|
||||
}
|
||||
|
||||
fun equal(source: BookSource): Boolean {
|
||||
return equal(bookSourceName, source.bookSourceName)
|
||||
&& equal(bookSourceUrl, source.bookSourceUrl)
|
||||
&& equal(bookSourceGroup, source.bookSourceGroup)
|
||||
&& bookSourceType == source.bookSourceType
|
||||
&& equal(bookUrlPattern, source.bookUrlPattern)
|
||||
&& equal(bookSourceComment, source.bookSourceComment)
|
||||
&& customOrder == source.customOrder
|
||||
&& enabled == source.enabled
|
||||
&& enabledExplore == source.enabledExplore
|
||||
&& enabledCookieJar == source.enabledCookieJar
|
||||
&& equal(variableComment, source.variableComment)
|
||||
&& equal(concurrentRate, source.concurrentRate)
|
||||
&& equal(jsLib, source.jsLib)
|
||||
&& equal(header, source.header)
|
||||
&& equal(loginUrl, source.loginUrl)
|
||||
&& equal(loginUi, source.loginUi)
|
||||
&& equal(loginCheckJs, source.loginCheckJs)
|
||||
&& equal(coverDecodeJs, source.coverDecodeJs)
|
||||
&& equal(exploreUrl, source.exploreUrl)
|
||||
&& equal(searchUrl, source.searchUrl)
|
||||
&& getSearchRule() == source.getSearchRule()
|
||||
&& getExploreRule() == source.getExploreRule()
|
||||
&& getBookInfoRule() == source.getBookInfoRule()
|
||||
&& getTocRule() == source.getTocRule()
|
||||
&& getContentRule() == source.getContentRule()
|
||||
}
|
||||
|
||||
private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
|
||||
|
||||
class Converters {
|
||||
|
||||
@TypeConverter
|
||||
fun exploreRuleToString(exploreRule: ExploreRule?): String =
|
||||
GSON.toJson(exploreRule)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToExploreRule(json: String?) =
|
||||
GSON.fromJsonObject<ExploreRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun searchRuleToString(searchRule: SearchRule?): String =
|
||||
GSON.toJson(searchRule)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToSearchRule(json: String?) =
|
||||
GSON.fromJsonObject<SearchRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String =
|
||||
GSON.toJson(bookInfoRule)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToBookInfoRule(json: String?) =
|
||||
GSON.fromJsonObject<BookInfoRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun tocRuleToString(tocRule: TocRule?): String =
|
||||
GSON.toJson(tocRule)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToTocRule(json: String?) =
|
||||
GSON.fromJsonObject<TocRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun contentRuleToString(contentRule: ContentRule?): String =
|
||||
GSON.toJson(contentRule)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToContentRule(json: String?) =
|
||||
GSON.fromJsonObject<ContentRule>(json).getOrNull()
|
||||
|
||||
@TypeConverter
|
||||
fun stringToReviewRule(json: String?): ReviewRule? = null
|
||||
|
||||
@TypeConverter
|
||||
fun reviewRuleToString(reviewRule: ReviewRule?): String = "null"
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.text.TextUtils
|
||||
import androidx.room.DatabaseView
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
|
||||
|
||||
@DatabaseView(
|
||||
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||
(loginUrl is not null and trim(loginUrl) <> '') hasLoginUrl, lastUpdateTime, respondTime, weight,
|
||||
(exploreUrl is not null and trim(exploreUrl) <> '') hasExploreUrl
|
||||
from book_sources""",
|
||||
viewName = "book_sources_part"
|
||||
)
|
||||
data class BookSourcePart(
|
||||
// 地址,包括 http/https
|
||||
var bookSourceUrl: String = "",
|
||||
// 名称
|
||||
var bookSourceName: String = "",
|
||||
// 分组
|
||||
var bookSourceGroup: String? = null,
|
||||
// 手动排序编号
|
||||
var customOrder: Int = 0,
|
||||
// 是否启用
|
||||
var enabled: Boolean = true,
|
||||
// 启用发现
|
||||
var enabledExplore: Boolean = true,
|
||||
// 是否有登录地址
|
||||
var hasLoginUrl: Boolean = false,
|
||||
// 最后更新时间,用于排序
|
||||
var lastUpdateTime: Long = 0,
|
||||
// 响应时间,用于排序
|
||||
var respondTime: Long = 180000L,
|
||||
// 智能排序的权重
|
||||
var weight: Int = 0,
|
||||
// 是否有发现url
|
||||
var hasExploreUrl: Boolean = false
|
||||
) {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return bookSourceUrl.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is BookSourcePart) other.bookSourceUrl == bookSourceUrl else false
|
||||
}
|
||||
|
||||
fun getDisPlayNameGroup(): String {
|
||||
return if (bookSourceGroup.isNullOrBlank()) {
|
||||
bookSourceName
|
||||
} else {
|
||||
String.format("%s (%s)", bookSourceName, bookSourceGroup)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBookSource(): BookSource? {
|
||||
return appDb.bookSourceDao.getBookSource(bookSourceUrl)
|
||||
}
|
||||
|
||||
fun addGroup(groups: String) {
|
||||
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))
|
||||
bookSourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
if (bookSourceGroup.isNullOrBlank()) bookSourceGroup = groups
|
||||
}
|
||||
|
||||
fun removeGroup(groups: String) {
|
||||
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())
|
||||
bookSourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun List<BookSourcePart>.toBookSource(): List<BookSource> {
|
||||
return mapNotNull { it.getBookSource() }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "bookmarks",
|
||||
indices = [(Index(value = ["bookName", "bookAuthor"], unique = false))]
|
||||
)
|
||||
data class Bookmark(
|
||||
@PrimaryKey
|
||||
val time: Long = System.currentTimeMillis(),
|
||||
val bookName: String = "",
|
||||
val bookAuthor: String = "",
|
||||
var chapterIndex: Int = 0,
|
||||
var chapterPos: Int = 0,
|
||||
var chapterName: String = "",
|
||||
var bookText: String = "",
|
||||
var content: String = ""
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "caches", indices = [(Index(value = ["key"], unique = true))])
|
||||
data class Cache(
|
||||
@PrimaryKey
|
||||
val key: String = "",
|
||||
var value: String? = null,
|
||||
var deadline: Long = 0L
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "cookies", indices = [(Index(value = ["url"], unique = true))])
|
||||
data class Cookie(
|
||||
@PrimaryKey
|
||||
var url: String = "",
|
||||
var cookie: String = ""
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.model.analyzeRule.AnalyzeRule
|
||||
import io.legato.kazusa.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
|
||||
import io.legato.kazusa.model.analyzeRule.AnalyzeUrl
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
/**
|
||||
* 字典规则
|
||||
*/
|
||||
@Entity(tableName = "dictRules")
|
||||
data class DictRule(
|
||||
@PrimaryKey
|
||||
var name: String = "",
|
||||
var urlRule: String = "",
|
||||
var showRule: String = "",
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var enabled: Boolean = true,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var sortNumber: Int = 0
|
||||
) {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return name.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is DictRule) {
|
||||
return name == other.name
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索字典
|
||||
*/
|
||||
suspend fun search(word: String): String {
|
||||
val analyzeUrl = AnalyzeUrl(urlRule, key = word, coroutineContext = coroutineContext)
|
||||
val body = analyzeUrl.getStrResponseAwait().body
|
||||
if (showRule.isBlank()) {
|
||||
return body!!
|
||||
}
|
||||
val analyzeRule = AnalyzeRule().setCoroutineContext(coroutineContext)
|
||||
return analyzeRule.getString(showRule, mContent = body)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.jayway.jsonpath.DocumentContext
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.jsonPath
|
||||
import io.legato.kazusa.utils.readLong
|
||||
import io.legato.kazusa.utils.readString
|
||||
|
||||
/**
|
||||
* 在线朗读引擎
|
||||
*/
|
||||
@Entity(tableName = "httpTTS")
|
||||
data class HttpTTS(
|
||||
@PrimaryKey
|
||||
val id: Long = System.currentTimeMillis(),
|
||||
var name: String = "",
|
||||
var url: String = "",
|
||||
var contentType: String? = null,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
override var concurrentRate: String? = "0",
|
||||
override var loginUrl: String? = null,
|
||||
override var loginUi: String? = null,
|
||||
override var header: String? = null,
|
||||
override var jsLib: String? = null,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
override var enabledCookieJar: Boolean? = false,
|
||||
var loginCheckJs: String? = null,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastUpdateTime: Long = System.currentTimeMillis()
|
||||
) : BaseSource {
|
||||
|
||||
override fun getTag(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getKey(): String {
|
||||
return "httpTts:$id"
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
companion object {
|
||||
|
||||
fun fromJsonDoc(doc: DocumentContext): Result<HttpTTS> {
|
||||
return kotlin.runCatching {
|
||||
val loginUi = doc.read<Any>("$.loginUi")
|
||||
HttpTTS(
|
||||
id = doc.readLong("$.id") ?: System.currentTimeMillis(),
|
||||
name = doc.readString("$.name")!!,
|
||||
url = doc.readString("$.url")!!,
|
||||
contentType = doc.readString("$.contentType"),
|
||||
concurrentRate = doc.readString("$.concurrentRate"),
|
||||
loginUrl = doc.readString("$.loginUrl"),
|
||||
loginUi = if (loginUi is List<*>) GSON.toJson(loginUi) else loginUi?.toString(),
|
||||
header = doc.readString("$.header"),
|
||||
loginCheckJs = doc.readString("$.loginCheckJs")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJson(json: String): Result<HttpTTS> {
|
||||
return fromJsonDoc(jsonPath.parse(json))
|
||||
}
|
||||
|
||||
fun fromJsonArray(jsonArray: String): Result<ArrayList<HttpTTS>> {
|
||||
return kotlin.runCatching {
|
||||
val sources = arrayListOf<HttpTTS>()
|
||||
val doc = jsonPath.parse(jsonArray).read<List<*>>("$")
|
||||
doc.forEach {
|
||||
val jsonItem = jsonPath.parse(it)
|
||||
fromJsonDoc(jsonItem).getOrThrow().let { source ->
|
||||
sources.add(source)
|
||||
}
|
||||
}
|
||||
return@runCatching sources
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "keyboardAssists", primaryKeys = ["type", "key"])
|
||||
data class KeyboardAssist(
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var type: Int = 0,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var key: String,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var value: String,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var serialNo: Int = 0
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(tableName = "readRecord", primaryKeys = ["deviceId", "bookName"])
|
||||
data class ReadRecord(
|
||||
var deviceId: String = "",
|
||||
var bookName: String = "",
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var readTime: Long = 0L,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastRead: Long = System.currentTimeMillis()
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
data class ReadRecordShow(
|
||||
var bookName: String,
|
||||
var readTime: Long,
|
||||
var lastRead: Long
|
||||
)
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.exception.NoStackTraceException
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import splitties.init.appCtx
|
||||
import java.util.regex.Pattern
|
||||
import java.util.regex.PatternSyntaxException
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "replace_rules",
|
||||
indices = [(Index(value = ["id"]))]
|
||||
)
|
||||
data class ReplaceRule(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = System.currentTimeMillis(),
|
||||
//名称
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var name: String = "",
|
||||
//分组
|
||||
var group: String? = null,
|
||||
//替换内容
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var pattern: String = "",
|
||||
//替换为
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var replacement: String = "",
|
||||
//作用范围
|
||||
var scope: String? = null,
|
||||
//作用于标题
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var scopeTitle: Boolean = false,
|
||||
//作用于正文
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var scopeContent: Boolean = true,
|
||||
//排除范围
|
||||
var excludeScope: String? = null,
|
||||
//是否启用
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var isEnabled: Boolean = true,
|
||||
//是否正则
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var isRegex: Boolean = true,
|
||||
//超时时间
|
||||
@ColumnInfo(defaultValue = "3000")
|
||||
var timeoutMillisecond: Long = 3000L,
|
||||
//排序
|
||||
@ColumnInfo(name = "sortOrder", defaultValue = "0")
|
||||
var order: Int = Int.MIN_VALUE
|
||||
) : Parcelable {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is ReplaceRule) {
|
||||
return other.id == id
|
||||
}
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
val regex: Regex by lazy {
|
||||
pattern.toRegex()
|
||||
}
|
||||
|
||||
fun getDisplayNameGroup(): String {
|
||||
return if (group.isNullOrBlank()) {
|
||||
name
|
||||
} else {
|
||||
String.format("%s (%s)", name, group)
|
||||
}
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
if (TextUtils.isEmpty(pattern)) {
|
||||
return false
|
||||
}
|
||||
//判断正则表达式是否正确
|
||||
if (isRegex) {
|
||||
try {
|
||||
Pattern.compile(pattern)
|
||||
} catch (ex: PatternSyntaxException) {
|
||||
AppLog.put("正则语法错误或不支持:${ex.localizedMessage}", ex)
|
||||
return false
|
||||
}
|
||||
// Pattern.compile测试通过,但是部分情况下会替换超时,报错,一般发生在修改表达式时漏删了
|
||||
if (pattern.endsWith('|') && !pattern.endsWith("\\|")) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(NoStackTraceException::class)
|
||||
fun checkValid() {
|
||||
if (!isValid()) {
|
||||
throw NoStackTraceException(appCtx.getString(R.string.replace_rule_invalid))
|
||||
}
|
||||
}
|
||||
|
||||
fun getValidTimeoutMillisecond(): Long {
|
||||
if (timeoutMillisecond <= 0) {
|
||||
return 3000L
|
||||
}
|
||||
return timeoutMillisecond
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
|
||||
@Entity(
|
||||
tableName = "rssArticles",
|
||||
primaryKeys = ["origin", "link"]
|
||||
)
|
||||
data class RssArticle(
|
||||
override var origin: String = "",
|
||||
var sort: String = "",
|
||||
var title: String = "",
|
||||
var order: Long = 0,
|
||||
override var link: String = "",
|
||||
var pubDate: String? = null,
|
||||
var description: String? = null,
|
||||
var content: String? = null,
|
||||
var image: String? = null,
|
||||
@ColumnInfo(defaultValue = "默认分组")
|
||||
var group: String = "默认分组",
|
||||
var read: Boolean = false,
|
||||
override var variable: String? = null
|
||||
) : BaseRssArticle {
|
||||
|
||||
override fun hashCode() = link.hashCode()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
other ?: return false
|
||||
return if (other is RssArticle) origin == other.origin && link == other.link else false
|
||||
}
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
override val variableMap: HashMap<String, String> by lazy {
|
||||
GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
|
||||
}
|
||||
|
||||
fun toStar() = RssStar(
|
||||
origin = origin,
|
||||
sort = sort,
|
||||
title = title,
|
||||
starTime = System.currentTimeMillis(),
|
||||
link = link,
|
||||
pubDate = pubDate,
|
||||
description = description,
|
||||
content = content,
|
||||
image = image,
|
||||
group = group,
|
||||
variable = variable
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "rssReadRecords")
|
||||
data class RssReadRecord(
|
||||
@PrimaryKey
|
||||
val record: String,
|
||||
val title: String? = null,
|
||||
val readTime: Long? = null,
|
||||
val read: Boolean = true
|
||||
)
|
||||
@@ -0,0 +1,182 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.constant.AppPattern
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "rssSources", indices = [(Index(value = ["sourceUrl"], unique = false))])
|
||||
data class RssSource(
|
||||
@PrimaryKey
|
||||
var sourceUrl: String = "",
|
||||
// 名称
|
||||
var sourceName: String = "",
|
||||
// 图标
|
||||
var sourceIcon: String = "",
|
||||
// 分组
|
||||
var sourceGroup: String? = null,
|
||||
// 注释
|
||||
var sourceComment: String? = null,
|
||||
// 是否启用
|
||||
var enabled: Boolean = true,
|
||||
// 自定义变量说明
|
||||
var variableComment: String? = null,
|
||||
// js库
|
||||
override var jsLib: String? = null,
|
||||
// 启用okhttp CookieJAr 自动保存每次请求的cookie
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
override var enabledCookieJar: Boolean? = true,
|
||||
/**并发率**/
|
||||
override var concurrentRate: String? = null,
|
||||
/**请求头**/
|
||||
override var header: String? = null,
|
||||
/**登录地址**/
|
||||
override var loginUrl: String? = null,
|
||||
/**登录Ui**/
|
||||
override var loginUi: String? = null,
|
||||
/**登录检测js**/
|
||||
var loginCheckJs: String? = null,
|
||||
/**封面解密js**/
|
||||
var coverDecodeJs: String? = null,
|
||||
/**分类Url**/
|
||||
var sortUrl: String? = null,
|
||||
/**是否单url源**/
|
||||
var singleUrl: Boolean = false,
|
||||
/*列表规则*/
|
||||
/**列表样式,0,1,2**/
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var articleStyle: Int = 0,
|
||||
/**列表规则**/
|
||||
var ruleArticles: String? = null,
|
||||
/**下一页规则**/
|
||||
var ruleNextPage: String? = null,
|
||||
/**标题规则**/
|
||||
var ruleTitle: String? = null,
|
||||
/**发布日期规则**/
|
||||
var rulePubDate: String? = null,
|
||||
/*webView规则*/
|
||||
/**描述规则**/
|
||||
var ruleDescription: String? = null,
|
||||
/**图片规则**/
|
||||
var ruleImage: String? = null,
|
||||
/**链接规则**/
|
||||
var ruleLink: String? = null,
|
||||
/**正文规则**/
|
||||
var ruleContent: String? = null,
|
||||
/**正文url白名单**/
|
||||
var contentWhitelist: String? = null,
|
||||
/**正文url黑名单**/
|
||||
var contentBlacklist: String? = null,
|
||||
/**
|
||||
* 跳转url拦截,
|
||||
* js, 返回true拦截,js变量url,可以通过js打开url,比如调用阅读搜索,添加书架等,简化规则写法,不用webView js注入
|
||||
* **/
|
||||
var shouldOverrideUrlLoading: String? = null,
|
||||
/**webView样式**/
|
||||
var style: String? = null,
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var enableJs: Boolean = true,
|
||||
@ColumnInfo(defaultValue = "1")
|
||||
var loadWithBaseUrl: Boolean = true,
|
||||
/**注入js**/
|
||||
var injectJs: String? = null,
|
||||
/*其它规则*/
|
||||
/**最后更新时间,用于排序**/
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var lastUpdateTime: Long = 0,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var customOrder: Int = 0
|
||||
) : Parcelable, BaseSource {
|
||||
|
||||
override fun getTag(): String {
|
||||
return sourceName
|
||||
}
|
||||
|
||||
override fun getKey(): String {
|
||||
return sourceUrl
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is RssSource) {
|
||||
return other.sourceUrl == sourceUrl
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode() = sourceUrl.hashCode()
|
||||
|
||||
fun equal(source: RssSource): Boolean {
|
||||
return equal(sourceUrl, source.sourceUrl)
|
||||
&& equal(sourceName, source.sourceName)
|
||||
&& equal(sourceIcon, source.sourceIcon)
|
||||
&& enabled == source.enabled
|
||||
&& equal(sourceGroup, source.sourceGroup)
|
||||
&& enabledCookieJar == source.enabledCookieJar
|
||||
&& equal(sourceComment, source.sourceComment)
|
||||
&& equal(concurrentRate, source.concurrentRate)
|
||||
&& equal(header, source.header)
|
||||
&& equal(loginUrl, source.loginUrl)
|
||||
&& equal(loginUi, source.loginUi)
|
||||
&& equal(loginCheckJs, source.loginCheckJs)
|
||||
&& equal(coverDecodeJs, source.coverDecodeJs)
|
||||
&& equal(sortUrl, source.sortUrl)
|
||||
&& singleUrl == source.singleUrl
|
||||
&& articleStyle == source.articleStyle
|
||||
&& equal(ruleArticles, source.ruleArticles)
|
||||
&& equal(ruleNextPage, source.ruleNextPage)
|
||||
&& equal(ruleTitle, source.ruleTitle)
|
||||
&& equal(rulePubDate, source.rulePubDate)
|
||||
&& equal(ruleDescription, source.ruleDescription)
|
||||
&& equal(ruleLink, source.ruleLink)
|
||||
&& equal(ruleContent, source.ruleContent)
|
||||
&& enableJs == source.enableJs
|
||||
&& loadWithBaseUrl == source.loadWithBaseUrl
|
||||
&& equal(variableComment, source.variableComment)
|
||||
&& equal(style, source.style)
|
||||
&& equal(injectJs, source.injectJs)
|
||||
}
|
||||
|
||||
private fun equal(a: String?, b: String?): Boolean {
|
||||
return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
|
||||
}
|
||||
|
||||
fun getDisplayNameGroup(): String {
|
||||
return if (sourceGroup.isNullOrBlank()) {
|
||||
sourceName
|
||||
} else {
|
||||
String.format("%s (%s)", sourceName, sourceGroup)
|
||||
}
|
||||
}
|
||||
|
||||
fun addGroup(groups: String): RssSource {
|
||||
sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))
|
||||
sourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
if (sourceGroup.isNullOrBlank()) sourceGroup = groups
|
||||
return this
|
||||
}
|
||||
|
||||
fun removeGroup(groups: String): RssSource {
|
||||
sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
|
||||
it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())
|
||||
sourceGroup = TextUtils.join(",", it)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun getDisplayVariableComment(otherComment: String): String {
|
||||
return if (variableComment.isNullOrBlank()) {
|
||||
otherComment
|
||||
} else {
|
||||
"${variableComment}\n$otherComment"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
|
||||
|
||||
@Entity(
|
||||
tableName = "rssStars",
|
||||
primaryKeys = ["origin", "link"]
|
||||
)
|
||||
data class RssStar(
|
||||
override var origin: String = "",
|
||||
var sort: String = "",
|
||||
var title: String = "",
|
||||
var starTime: Long = 0,
|
||||
override var link: String = "",
|
||||
var pubDate: String? = null,
|
||||
var description: String? = null,
|
||||
var content: String? = null,
|
||||
var image: String? = null,
|
||||
@ColumnInfo(defaultValue = "默认分组")
|
||||
var group: String = "默认分组",
|
||||
override var variable: String? = null
|
||||
) : BaseRssArticle {
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
override val variableMap by lazy {
|
||||
GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()
|
||||
}
|
||||
|
||||
fun toRssArticle() = RssArticle(
|
||||
origin = origin,
|
||||
sort = sort,
|
||||
title = title,
|
||||
link = link,
|
||||
pubDate = pubDate,
|
||||
description = description,
|
||||
content = content,
|
||||
image = image,
|
||||
group = group,
|
||||
variable = variable
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "ruleSubs")
|
||||
data class RuleSub(
|
||||
@PrimaryKey
|
||||
val id: Long = System.currentTimeMillis(),
|
||||
var name: String = "",
|
||||
var url: String = "",
|
||||
var type: Int = 0,
|
||||
var customOrder: Int = 0,
|
||||
var autoUpdate: Boolean = false,
|
||||
var update: Long = System.currentTimeMillis()
|
||||
)
|
||||
@@ -0,0 +1,139 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "searchBooks",
|
||||
indices = [(Index(value = ["bookUrl"], unique = true)),
|
||||
(Index(value = ["origin"], unique = false))],
|
||||
foreignKeys = [(ForeignKey(
|
||||
entity = BookSource::class,
|
||||
parentColumns = ["bookSourceUrl"],
|
||||
childColumns = ["origin"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
))]
|
||||
)
|
||||
data class SearchBook(
|
||||
@PrimaryKey
|
||||
override var bookUrl: String = "",
|
||||
/** 书源 */
|
||||
var origin: String = "",
|
||||
var originName: String = "",
|
||||
/** BookType */
|
||||
var type: Int = BookType.text,
|
||||
override var name: String = "",
|
||||
override var author: String = "",
|
||||
override var kind: String? = null,
|
||||
var coverUrl: String? = null,
|
||||
var intro: String? = null,
|
||||
override var wordCount: String? = null,
|
||||
var latestChapterTitle: String? = null,
|
||||
/** 目录页Url (toc=table of Contents) */
|
||||
var tocUrl: String = "",
|
||||
var time: Long = System.currentTimeMillis(),
|
||||
override var variable: String? = null,
|
||||
var originOrder: Int = 0,
|
||||
var chapterWordCountText: String? = null,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
var chapterWordCount: Int = -1,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
var respondTime: Int = -1
|
||||
) : Parcelable, BaseBook, Comparable<SearchBook> {
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
override var infoHtml: String? = null
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
override var tocHtml: String? = null
|
||||
|
||||
override fun equals(other: Any?) = other is SearchBook && other.bookUrl == bookUrl
|
||||
|
||||
override fun hashCode() = bookUrl.hashCode()
|
||||
|
||||
override fun compareTo(other: SearchBook): Int {
|
||||
return other.originOrder - this.originOrder
|
||||
}
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
override val variableMap: HashMap<String, String> by lazy {
|
||||
GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: HashMap()
|
||||
}
|
||||
|
||||
@delegate:Transient
|
||||
@delegate:Ignore
|
||||
@IgnoredOnParcel
|
||||
val origins: LinkedHashSet<String> by lazy { linkedSetOf(origin) }
|
||||
|
||||
fun addOrigin(origin: String) {
|
||||
origins.add(origin)
|
||||
}
|
||||
|
||||
fun getDisplayLastChapterTitle(): String {
|
||||
latestChapterTitle?.let {
|
||||
if (it.isNotEmpty()) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return "无最新章节"
|
||||
}
|
||||
|
||||
fun trimIntro(context: Context): String {
|
||||
val trimIntro = intro?.trim()
|
||||
return if (trimIntro.isNullOrEmpty()) {
|
||||
context.getString(R.string.intro_show_null)
|
||||
} else {
|
||||
context.getString(R.string.intro_show, trimIntro)
|
||||
}
|
||||
}
|
||||
|
||||
fun releaseHtmlData() {
|
||||
infoHtml = null
|
||||
tocHtml = null
|
||||
}
|
||||
|
||||
fun primaryStr(): String {
|
||||
return origin + bookUrl
|
||||
}
|
||||
|
||||
fun sameBookTypeLocal(bookType: Int): Boolean {
|
||||
return type and BookType.allBookTypeLocal == bookType and BookType.allBookTypeLocal
|
||||
}
|
||||
|
||||
fun toBook() = Book(
|
||||
name = name,
|
||||
author = author,
|
||||
kind = kind,
|
||||
bookUrl = bookUrl,
|
||||
origin = origin,
|
||||
originName = originName,
|
||||
type = type,
|
||||
wordCount = wordCount,
|
||||
latestChapterTitle = latestChapterTitle,
|
||||
coverUrl = coverUrl,
|
||||
intro = intro,
|
||||
tocUrl = tocUrl,
|
||||
originOrder = originOrder,
|
||||
variable = variable
|
||||
).apply {
|
||||
this.infoHtml = this@SearchBook.infoHtml
|
||||
this.tocHtml = this@SearchBook.tocHtml
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "search_keywords", indices = [(Index(value = ["word"], unique = true))])
|
||||
data class SearchKeyword(
|
||||
/** 搜索关键词 */
|
||||
@PrimaryKey
|
||||
var word: String = "",
|
||||
/** 使用次数 */
|
||||
var usage: Int = 1,
|
||||
/** 最后一次使用时间 */
|
||||
var lastUseTime: Long = System.currentTimeMillis()
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.fromJsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* 服务器
|
||||
*/
|
||||
@Parcelize
|
||||
@Entity(tableName = "servers")
|
||||
data class Server(
|
||||
@PrimaryKey
|
||||
var id: Long = System.currentTimeMillis(),
|
||||
var name: String = "",
|
||||
var type: TYPE = TYPE.WEBDAV,
|
||||
var config: String? = null,
|
||||
var sortNumber: Int = 0
|
||||
) : Parcelable {
|
||||
|
||||
enum class TYPE {
|
||||
WEBDAV
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is Server) {
|
||||
return id == other.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getConfigJsonObject(): JSONObject? {
|
||||
val json = config
|
||||
json ?: return null
|
||||
return JSONObject(json)
|
||||
}
|
||||
|
||||
fun getWebDavConfig(): WebDavConfig? {
|
||||
return if (type == TYPE.WEBDAV) GSON.fromJsonObject<WebDavConfig>(config).getOrNull() else null
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class WebDavConfig(
|
||||
var url: String,
|
||||
var username: String,
|
||||
var password: String
|
||||
) : Parcelable
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.legato.kazusa.data.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
|
||||
@Entity(tableName = "txtTocRules")
|
||||
data class TxtTocRule(
|
||||
@PrimaryKey
|
||||
var id: Long = System.currentTimeMillis(),
|
||||
var name: String = "",
|
||||
var rule: String = "",
|
||||
var example: String? = null,
|
||||
var serialNumber: Int = -1,
|
||||
var enable: Boolean = true
|
||||
) {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is TxtTocRule) {
|
||||
return id == other.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 书籍详情页规则
|
||||
*/
|
||||
@Parcelize
|
||||
data class BookInfoRule(
|
||||
var init: String? = null,
|
||||
var name: String? = null,
|
||||
var author: String? = null,
|
||||
var intro: String? = null,
|
||||
var kind: String? = null,
|
||||
var lastChapter: String? = null,
|
||||
var updateTime: String? = null,
|
||||
var coverUrl: String? = null,
|
||||
var tocUrl: String? = null,
|
||||
var wordCount: String? = null,
|
||||
var canReName: String? = null,
|
||||
var downloadUrls: String? = null
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<BookInfoRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, BookInfoRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(
|
||||
json.asString,
|
||||
BookInfoRule::class.java
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
/**
|
||||
* 书籍列表规则
|
||||
*/
|
||||
interface BookListRule {
|
||||
var bookList: String?
|
||||
var name: String?
|
||||
var author: String?
|
||||
var intro: String?
|
||||
var kind: String?
|
||||
var lastChapter: String?
|
||||
var updateTime: String?
|
||||
var bookUrl: String?
|
||||
var coverUrl: String?
|
||||
var wordCount: String?
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 正文处理规则
|
||||
*/
|
||||
@Parcelize
|
||||
data class ContentRule(
|
||||
var content: String? = null,
|
||||
var title: String? = null, //有些网站只能在正文中获取标题
|
||||
var nextContentUrl: String? = null,
|
||||
var webJs: String? = null,
|
||||
var sourceRegex: String? = null,
|
||||
var replaceRegex: String? = null, //替换规则
|
||||
var imageStyle: String? = null, //默认大小居中,FULL最大宽度
|
||||
var imageDecode: String? = null, //图片bytes二次解密js, 返回解密后的bytes
|
||||
var payAction: String? = null, //购买操作,js或者包含{{js}}的url
|
||||
) : Parcelable {
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<ContentRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, ContentRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(
|
||||
json.asString,
|
||||
ContentRule::class.java
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
/**
|
||||
* 发现分类
|
||||
*/
|
||||
data class ExploreKind(
|
||||
val title: String = "",
|
||||
val url: String? = null,
|
||||
val style: FlexChildStyle? = null
|
||||
) {
|
||||
|
||||
fun style(): FlexChildStyle {
|
||||
return style ?: FlexChildStyle.defaultStyle
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 发现结果规则
|
||||
*/
|
||||
@Parcelize
|
||||
data class ExploreRule(
|
||||
override var bookList: String? = null,
|
||||
override var name: String? = null,
|
||||
override var author: String? = null,
|
||||
override var intro: String? = null,
|
||||
override var kind: String? = null,
|
||||
override var lastChapter: String? = null,
|
||||
override var updateTime: String? = null,
|
||||
override var bookUrl: String? = null,
|
||||
override var coverUrl: String? = null,
|
||||
override var wordCount: String? = null
|
||||
) : BookListRule, Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<ExploreRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, ExploreRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(
|
||||
json.asString,
|
||||
ExploreRule::class.java
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.view.View
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
|
||||
data class FlexChildStyle(
|
||||
val layout_flexGrow: Float = 0F,
|
||||
val layout_flexShrink: Float = 1F,
|
||||
val layout_alignSelf: String = "auto",
|
||||
val layout_flexBasisPercent: Float = -1F,
|
||||
val layout_wrapBefore: Boolean = false,
|
||||
) {
|
||||
|
||||
fun alignSelf(): Int {
|
||||
return when (layout_alignSelf) {
|
||||
"auto" -> -1
|
||||
"flex_start" -> 0
|
||||
"flex_end" -> 1
|
||||
"center" -> 2
|
||||
"baseline" -> 3
|
||||
"stretch" -> 4
|
||||
else -> -1
|
||||
}
|
||||
}
|
||||
|
||||
fun apply(view: View) {
|
||||
val lp = view.layoutParams as FlexboxLayout.LayoutParams
|
||||
lp.flexGrow = layout_flexGrow
|
||||
lp.flexShrink = layout_flexShrink
|
||||
lp.alignSelf = alignSelf()
|
||||
lp.flexBasisPercent = layout_flexBasisPercent
|
||||
lp.isWrapBefore = layout_wrapBefore
|
||||
}
|
||||
|
||||
companion object {
|
||||
val defaultStyle = FlexChildStyle()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ReviewRule(
|
||||
var reviewUrl: String? = null, // 段评URL
|
||||
var avatarRule: String? = null, // 段评发布者头像
|
||||
var contentRule: String? = null, // 段评内容
|
||||
var postTimeRule: String? = null, // 段评发布时间
|
||||
var reviewQuoteUrl: String? = null, // 获取段评回复URL
|
||||
|
||||
// 这些功能将在以上功能完成以后实现
|
||||
var voteUpUrl: String? = null, // 点赞URL
|
||||
var voteDownUrl: String? = null, // 点踩URL
|
||||
var postReviewUrl: String? = null, // 发送回复URL
|
||||
var postQuoteUrl: String? = null, // 发送回复段评URL
|
||||
var deleteUrl: String? = null, // 删除段评URL
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<ReviewRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, ReviewRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, ReviewRule::class.java)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
data class RowUi(
|
||||
var name: String,
|
||||
var type: String = "text",
|
||||
var action: String? = null,
|
||||
var style: FlexChildStyle? = null
|
||||
) {
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
object Type {
|
||||
|
||||
const val text = "text"
|
||||
const val password = "password"
|
||||
const val button = "button"
|
||||
|
||||
}
|
||||
|
||||
fun style(): FlexChildStyle {
|
||||
return style ?: FlexChildStyle.defaultStyle
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* 搜索结果处理规则
|
||||
*/
|
||||
@Parcelize
|
||||
data class SearchRule(
|
||||
/**校验关键字**/
|
||||
var checkKeyWord: String? = null,
|
||||
override var bookList: String? = null,
|
||||
override var name: String? = null,
|
||||
override var author: String? = null,
|
||||
override var intro: String? = null,
|
||||
override var kind: String? = null,
|
||||
override var lastChapter: String? = null,
|
||||
override var updateTime: String? = null,
|
||||
override var bookUrl: String? = null,
|
||||
override var coverUrl: String? = null,
|
||||
override var wordCount: String? = null
|
||||
) : BookListRule, Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<SearchRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, SearchRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, SearchRule::class.java)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.legato.kazusa.data.entities.rule
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.JsonDeserializer
|
||||
import io.legato.kazusa.utils.INITIAL_GSON
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class TocRule(
|
||||
var preUpdateJs: String? = null,
|
||||
var chapterList: String? = null,
|
||||
var chapterName: String? = null,
|
||||
var chapterUrl: String? = null,
|
||||
var formatJs: String? = null,
|
||||
var isVolume: String? = null,
|
||||
var isVip: String? = null,
|
||||
var isPay: String? = null,
|
||||
var updateTime: String? = null,
|
||||
var nextTocUrl: String? = null
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
val jsonDeserializer = JsonDeserializer<TocRule?> { json, _, _ ->
|
||||
when {
|
||||
json.isJsonObject -> INITIAL_GSON.fromJson(json, TocRule::class.java)
|
||||
json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, TocRule::class.java)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user