mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor: Migrate "database migrations" to use SQLDelight (#73)
* chore: Preparing SQLDelight * chore: Specify some config for SQLDelight * fix: Commit the thing bruh * refactor: Migrate (some) sql to SQLDelight * refactor: Migrate the rest of sql migration to SQLDelight * chore: Update SQLite to v3.45.0 * refactor: Retrofitting StorIO to work with SQLDelight * refactor: Removed unnecessary code, already handled by AndroidSqliteDriver * fix: Database lib too old to use FrameworkSQLiteOpenHelper * chore: Revert downgrade
This commit is contained in:
parent
d0712bde73
commit
41a46ba0f8
39 changed files with 604 additions and 273 deletions
|
@ -7,6 +7,7 @@ plugins {
|
|||
kotlin("plugin.serialization")
|
||||
id("kotlin-parcelize")
|
||||
id("com.google.android.gms.oss-licenses-plugin")
|
||||
id("app.cash.sqldelight")
|
||||
id("com.google.gms.google-services") apply false
|
||||
id("com.google.firebase.crashlytics") apply false
|
||||
}
|
||||
|
@ -142,6 +143,16 @@ android {
|
|||
jvmTarget = "17"
|
||||
}
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
|
||||
sqldelight {
|
||||
databases {
|
||||
create("Database") {
|
||||
packageName.set("tachiyomi.data")
|
||||
dialect(libs.sqldelight.dialects.sql)
|
||||
schemaOutputDirectory.set(project.file("./src/main/sqldelight"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -208,7 +219,9 @@ dependencies {
|
|||
implementation(libs.play.services.gcm)
|
||||
|
||||
// Database
|
||||
implementation(libs.bundles.db)
|
||||
implementation(libs.sqlite.android)
|
||||
implementation(libs.bundles.sqlite)
|
||||
//noinspection UseTomlInstead
|
||||
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
||||
//noinspection UseTomlInstead
|
||||
|
|
93
app/src/main/java/dev/yokai/data/AndroidDatabaseHandler.kt
Normal file
93
app/src/main/java/dev/yokai/data/AndroidDatabaseHandler.kt
Normal file
|
@ -0,0 +1,93 @@
|
|||
package dev.yokai.data
|
||||
|
||||
import app.cash.sqldelight.Query
|
||||
import app.cash.sqldelight.coroutines.asFlow
|
||||
import app.cash.sqldelight.coroutines.mapToList
|
||||
import app.cash.sqldelight.coroutines.mapToOne
|
||||
import app.cash.sqldelight.coroutines.mapToOneOrNull
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.data.Database
|
||||
|
||||
class AndroidDatabaseHandler(
|
||||
val db: Database,
|
||||
private val driver: SqlDriver,
|
||||
val queryDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
val transactionDispatcher: CoroutineDispatcher = queryDispatcher
|
||||
) : DatabaseHandler {
|
||||
|
||||
val suspendingTransactionId = ThreadLocal<Int>()
|
||||
|
||||
override suspend fun <T> await(inTransaction: Boolean, block: suspend Database.() -> T): T {
|
||||
return dispatch(inTransaction, block)
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> awaitList(
|
||||
inTransaction: Boolean,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): List<T> {
|
||||
return dispatch(inTransaction) { block(db).executeAsList() }
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> awaitOne(
|
||||
inTransaction: Boolean,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): T {
|
||||
return dispatch(inTransaction) { block(db).executeAsOne() }
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> awaitOneOrNull(
|
||||
inTransaction: Boolean,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): T? {
|
||||
return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
|
||||
}
|
||||
|
||||
override fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>> {
|
||||
return block(db).asFlow().mapToList(queryDispatcher)
|
||||
}
|
||||
|
||||
override fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T> {
|
||||
return block(db).asFlow().mapToOne(queryDispatcher)
|
||||
}
|
||||
|
||||
override fun <T : Any> subscribeToOneOrNull(block: Database.() -> Query<T>): Flow<T?> {
|
||||
return block(db).asFlow().mapToOneOrNull(queryDispatcher)
|
||||
}
|
||||
|
||||
/*
|
||||
override fun <T : Any> subscribeToPagingSource(
|
||||
countQuery: Database.() -> Query<Long>,
|
||||
transacter: Database.() -> Transacter,
|
||||
queryProvider: Database.(Long, Long) -> Query<T>
|
||||
): PagingSource<Long, T> {
|
||||
return QueryPagingSource(
|
||||
countQuery = countQuery(db),
|
||||
transacter = transacter(db),
|
||||
dispatcher = queryDispatcher,
|
||||
queryProvider = { limit, offset ->
|
||||
queryProvider.invoke(db, limit, offset)
|
||||
}
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
private suspend fun <T> dispatch(inTransaction: Boolean, block: suspend Database.() -> T): T {
|
||||
// Create a transaction if needed and run the calling block inside it.
|
||||
if (inTransaction) {
|
||||
return withTransaction { block(db) }
|
||||
}
|
||||
|
||||
// If we're currently in the transaction thread, there's no need to dispatch our query.
|
||||
if (driver.currentTransaction() != null) {
|
||||
return block(db)
|
||||
}
|
||||
|
||||
// Get the current database context and run the calling block.
|
||||
val context = getCurrentDatabaseContext()
|
||||
return withContext(context) { block(db) }
|
||||
}
|
||||
}
|
20
app/src/main/java/dev/yokai/data/DatabaseAdapter.kt
Normal file
20
app/src/main/java/dev/yokai/data/DatabaseAdapter.kt
Normal file
|
@ -0,0 +1,20 @@
|
|||
package dev.yokai.data
|
||||
|
||||
import app.cash.sqldelight.ColumnAdapter
|
||||
import java.util.Date
|
||||
|
||||
val dateAdapter = object : ColumnAdapter<Date, Long> {
|
||||
override fun decode(databaseValue: Long): Date = Date(databaseValue)
|
||||
override fun encode(value: Date): Long = value.time
|
||||
}
|
||||
|
||||
private const val listOfStringsSeparator = ", "
|
||||
val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
|
||||
override fun decode(databaseValue: String) =
|
||||
if (databaseValue.isEmpty()) {
|
||||
listOf()
|
||||
} else {
|
||||
databaseValue.split(listOfStringsSeparator)
|
||||
}
|
||||
override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsSeparator)
|
||||
}
|
39
app/src/main/java/dev/yokai/data/DatabaseHandler.kt
Normal file
39
app/src/main/java/dev/yokai/data/DatabaseHandler.kt
Normal file
|
@ -0,0 +1,39 @@
|
|||
package dev.yokai.data
|
||||
|
||||
import app.cash.sqldelight.Query
|
||||
import app.cash.sqldelight.Transacter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.data.Database
|
||||
|
||||
interface DatabaseHandler {
|
||||
suspend fun <T> await(inTransaction: Boolean = false, block: suspend Database.() -> T): T
|
||||
|
||||
suspend fun <T : Any> awaitList(
|
||||
inTransaction: Boolean = false,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): List<T>
|
||||
|
||||
suspend fun <T : Any> awaitOne(
|
||||
inTransaction: Boolean = false,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): T
|
||||
|
||||
suspend fun <T : Any> awaitOneOrNull(
|
||||
inTransaction: Boolean = false,
|
||||
block: suspend Database.() -> Query<T>
|
||||
): T?
|
||||
|
||||
fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>>
|
||||
|
||||
fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T>
|
||||
|
||||
fun <T : Any> subscribeToOneOrNull(block: Database.() -> Query<T>): Flow<T?>
|
||||
|
||||
/*
|
||||
fun <T : Any> subscribeToPagingSource(
|
||||
countQuery: Database.() -> Query<Long>,
|
||||
transacter: Database.() -> Transacter,
|
||||
queryProvider: Database.(Long, Long) -> Query<T>
|
||||
): PagingSource<Long, T>
|
||||
*/
|
||||
}
|
161
app/src/main/java/dev/yokai/data/TransactionContext.kt
Normal file
161
app/src/main/java/dev/yokai/data/TransactionContext.kt
Normal file
|
@ -0,0 +1,161 @@
|
|||
package dev.yokai.data
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asContextElement
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.RejectedExecutionException
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.ContinuationInterceptor
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
* Returns the transaction dispatcher if we are on a transaction, or the database dispatchers.
|
||||
*/
|
||||
internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): CoroutineContext {
|
||||
return coroutineContext[TransactionElement]?.transactionDispatcher ?: queryDispatcher
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the specified suspending [block] in a database transaction. The transaction will be
|
||||
* marked as successful unless an exception is thrown in the suspending [block] or the coroutine
|
||||
* is cancelled.
|
||||
*
|
||||
* SQLDelight will only perform at most one transaction at a time, additional transactions are queued
|
||||
* and executed on a first come, first serve order.
|
||||
*
|
||||
* Performing blocking database operations is not permitted in a coroutine scope other than the
|
||||
* one received by the suspending block. It is recommended that all [Dao] function invoked within
|
||||
* the [block] be suspending functions.
|
||||
*
|
||||
* The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor.
|
||||
*/
|
||||
internal suspend fun <T> AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T {
|
||||
// Use inherited transaction context if available, this allows nested suspending transactions.
|
||||
val transactionContext =
|
||||
coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext()
|
||||
return withContext(transactionContext) {
|
||||
val transactionElement = coroutineContext[TransactionElement]!!
|
||||
transactionElement.acquire()
|
||||
try {
|
||||
db.transactionWithResult {
|
||||
runBlocking(transactionContext) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
transactionElement.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a [CoroutineContext] for performing database operations within a coroutine transaction.
|
||||
*
|
||||
* The context is a combination of a dispatcher, a [TransactionElement] and a thread local element.
|
||||
*
|
||||
* * The dispatcher will dispatch coroutines to a single thread that is taken over from the SQLDelight
|
||||
* query executor. If the coroutine context is switched, suspending DAO functions will be able to
|
||||
* dispatch to the transaction thread.
|
||||
*
|
||||
* * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a
|
||||
* switch of context, suspending DAO methods will be able to use the indicator to dispatch the
|
||||
* database operation to the transaction thread.
|
||||
*
|
||||
* * The thread local element serves as a second indicator and marks threads that are used to
|
||||
* execute coroutines within the coroutine transaction, more specifically it allows us to identify
|
||||
* if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to
|
||||
* this value, for now all we care is if its present or not.
|
||||
*/
|
||||
private suspend fun AndroidDatabaseHandler.createTransactionContext(): CoroutineContext {
|
||||
val controlJob = Job()
|
||||
// make sure to tie the control job to this context to avoid blocking the transaction if
|
||||
// context get cancelled before we can even start using this job. Otherwise, the acquired
|
||||
// transaction thread will forever wait for the controlJob to be cancelled.
|
||||
// see b/148181325
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
controlJob.cancel()
|
||||
}
|
||||
|
||||
val dispatcher = transactionDispatcher.acquireTransactionThread(controlJob)
|
||||
val transactionElement = TransactionElement(controlJob, dispatcher)
|
||||
val threadLocalElement =
|
||||
suspendingTransactionId.asContextElement(System.identityHashCode(controlJob))
|
||||
return dispatcher + transactionElement + threadLocalElement
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a thread from the executor and returns a [ContinuationInterceptor] to dispatch
|
||||
* coroutines to the acquired thread. The [controlJob] is used to control the release of the
|
||||
* thread by cancelling the job.
|
||||
*/
|
||||
private suspend fun CoroutineDispatcher.acquireTransactionThread(
|
||||
controlJob: Job
|
||||
): ContinuationInterceptor {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
continuation.invokeOnCancellation {
|
||||
// We got cancelled while waiting to acquire a thread, we can't stop our attempt to
|
||||
// acquire a thread, but we can cancel the controlling job so once it gets acquired it
|
||||
// is quickly released.
|
||||
controlJob.cancel()
|
||||
}
|
||||
try {
|
||||
dispatch(EmptyCoroutineContext) {
|
||||
runBlocking {
|
||||
// Thread acquired, resume coroutine.
|
||||
continuation.resume(coroutineContext[ContinuationInterceptor]!!)
|
||||
controlJob.join()
|
||||
}
|
||||
}
|
||||
} catch (ex: RejectedExecutionException) {
|
||||
// Couldn't acquire a thread, cancel coroutine.
|
||||
continuation.cancel(
|
||||
IllegalStateException(
|
||||
"Unable to acquire a thread to perform the database transaction.", ex
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [CoroutineContext.Element] that indicates there is an on-going database transaction.
|
||||
*/
|
||||
private class TransactionElement(
|
||||
private val transactionThreadControlJob: Job,
|
||||
val transactionDispatcher: ContinuationInterceptor
|
||||
) : CoroutineContext.Element {
|
||||
|
||||
companion object Key : CoroutineContext.Key<TransactionElement>
|
||||
|
||||
override val key: CoroutineContext.Key<TransactionElement>
|
||||
get() = TransactionElement
|
||||
|
||||
/**
|
||||
* Number of transactions (including nested ones) started with this element.
|
||||
* Call [acquire] to increase the count and [release] to decrease it. If the count reaches zero
|
||||
* when [release] is invoked then the transaction job is cancelled and the transaction thread
|
||||
* is released.
|
||||
*/
|
||||
private val referenceCount = AtomicInteger(0)
|
||||
|
||||
fun acquire() {
|
||||
referenceCount.incrementAndGet()
|
||||
}
|
||||
|
||||
fun release() {
|
||||
val count = referenceCount.decrementAndGet()
|
||||
if (count < 0) {
|
||||
throw IllegalStateException("Transaction was never started or was already released.")
|
||||
} else if (count == 0) {
|
||||
// Cancel the job that controls the transaction thread, causing it to be released.
|
||||
transactionThreadControlJob.cancel()
|
||||
}
|
||||
}
|
||||
}
|
28
app/src/main/java/dev/yokai/data/manga/MangaMapper.kt
Normal file
28
app/src/main/java/dev/yokai/data/manga/MangaMapper.kt
Normal file
|
@ -0,0 +1,28 @@
|
|||
package dev.yokai.data.manga
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.updateStrategyAdapter
|
||||
|
||||
val mangaMapper: (Long, Long, String, String?, String?, String?, String?, String, Int, String?, Boolean, Long, Boolean, Int, Int, Boolean, Long, String?, Int) -> Manga =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, initialized, viewerFlags, chapterFlags, hideTitle, dateAdded, filteredScanlators, updateStrategy ->
|
||||
Manga.create(source).apply {
|
||||
this.id = id
|
||||
this.url = url
|
||||
this.artist = artist
|
||||
this.author = author
|
||||
this.description = description
|
||||
this.genre = genre
|
||||
this.title = title
|
||||
this.status = status
|
||||
this.thumbnail_url = thumbnailUrl
|
||||
this.favorite = favorite
|
||||
this.last_update = lastUpdate
|
||||
this.initialized = initialized
|
||||
this.viewer_flags = viewerFlags
|
||||
this.chapter_flags = chapterFlags
|
||||
this.hide_title = hideTitle
|
||||
this.date_added = dateAdded
|
||||
this.filtered_scanlators = filteredScanlators
|
||||
this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode)
|
||||
}
|
||||
}
|
|
@ -3,21 +3,7 @@ package eu.kanade.tachiyomi.data.database
|
|||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import java.util.Date
|
||||
|
||||
val dateAdapter = object : ColumnAdapter<Date, Long> {
|
||||
override fun decode(databaseValue: Long): Date = Date(databaseValue)
|
||||
override fun encode(value: Date): Long = value.time
|
||||
}
|
||||
|
||||
private const val listOfStringsSeparator = ", "
|
||||
val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
|
||||
override fun decode(databaseValue: String) =
|
||||
if (databaseValue.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
databaseValue.split(listOfStringsSeparator)
|
||||
}
|
||||
override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsSeparator)
|
||||
}
|
||||
// TODO: Move to dev.yokai.data.DatabaseAdapter
|
||||
|
||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Int> {
|
||||
private val enumValues by lazy { UpdateStrategy.entries }
|
||||
|
|
|
@ -29,7 +29,10 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
|||
/**
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
open class DatabaseHelper(
|
||||
context: Context,
|
||||
openHelper: SupportSQLiteOpenHelper,
|
||||
) :
|
||||
MangaQueries,
|
||||
ChapterQueries,
|
||||
TrackQueries,
|
||||
|
@ -38,13 +41,8 @@ open class DatabaseHelper(context: Context) :
|
|||
HistoryQueries,
|
||||
SearchMetadataQueries {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
.callback(DbOpenCallback())
|
||||
.build()
|
||||
|
||||
override val db = DefaultStorIOSQLite.builder()
|
||||
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
||||
.sqliteOpenHelper(openHelper)
|
||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||
|
|
|
@ -1,26 +1,17 @@
|
|||
package eu.kanade.tachiyomi.data.database
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
import tachiyomi.data.Database
|
||||
import timber.log.Timber
|
||||
|
||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
class DbOpenCallback : AndroidSqliteDriver.Callback(Database.Schema) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Name of the database file.
|
||||
*/
|
||||
const val DATABASE_NAME = "tachiyomi.db"
|
||||
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = 17
|
||||
}
|
||||
|
||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
||||
|
@ -36,84 +27,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||
cursor.close()
|
||||
}
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||
execSQL(MangaTable.createTableQuery)
|
||||
execSQL(ChapterTable.createTableQuery)
|
||||
execSQL(TrackTable.createTableQuery)
|
||||
execSQL(CategoryTable.createTableQuery)
|
||||
execSQL(MangaCategoryTable.createTableQuery)
|
||||
execSQL(HistoryTable.createTableQuery)
|
||||
|
||||
// DB indexes
|
||||
execSQL(MangaTable.createUrlIndexQuery)
|
||||
execSQL(MangaTable.createLibraryIndexQuery)
|
||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
Timber.d("Creating new database...")
|
||||
super.onCreate(db)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||
|
||||
// Fix kissmanga covers after supporting cloudflare
|
||||
db.execSQL(
|
||||
"""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""",
|
||||
)
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
db.execSQL(HistoryTable.createTableQuery)
|
||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(ChapterTable.bookmarkUpdateQuery)
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.execSQL(ChapterTable.addScanlator)
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
db.execSQL(TrackTable.addTrackingUrl)
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
db.execSQL(TrackTable.addLibraryId)
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
db.execSQL("DROP INDEX IF EXISTS mangas_favorite_index")
|
||||
db.execSQL(MangaTable.createLibraryIndexQuery)
|
||||
db.execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
db.execSQL(MangaTable.addHideTitle)
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
db.execSQL(CategoryTable.addMangaOrder)
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
db.execSQL(ChapterTable.pagesLeftQuery)
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
db.execSQL(MangaTable.addDateAddedCol)
|
||||
}
|
||||
if (oldVersion < 13) {
|
||||
db.execSQL(TrackTable.addStartDate)
|
||||
db.execSQL(TrackTable.addFinishDate)
|
||||
}
|
||||
if (oldVersion < 14) {
|
||||
db.execSQL(MangaTable.addFilteredScanlators)
|
||||
}
|
||||
if (oldVersion < 15) {
|
||||
db.execSQL(TrackTable.renameTableToTemp)
|
||||
db.execSQL(TrackTable.createTableQuery)
|
||||
db.execSQL(TrackTable.insertFromTempTable)
|
||||
db.execSQL(TrackTable.dropTempTable)
|
||||
}
|
||||
if (oldVersion < 16) {
|
||||
db.execSQL(MangaTable.addUpdateStrategy)
|
||||
}
|
||||
if (oldVersion < 17) {
|
||||
db.execSQL(TrackTable.updateMangaUpdatesScore)
|
||||
if (oldVersion < newVersion) {
|
||||
Timber.d("Upgrading database from $oldVersion to $newVersion")
|
||||
super.onUpgrade(db, oldVersion, newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,4 @@ object CategoryTable {
|
|||
|
||||
const val COL_MANGA_ORDER = "manga_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_ORDER INTEGER NOT NULL,
|
||||
$COL_FLAGS INTEGER NOT NULL,
|
||||
$COL_MANGA_ORDER TEXT NOT NULL
|
||||
)"""
|
||||
|
||||
val addMangaOrder: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT"
|
||||
}
|
||||
|
|
|
@ -30,42 +30,4 @@ object ChapterTable {
|
|||
|
||||
const val COL_SOURCE_ORDER = "source_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_SCANLATOR TEXT,
|
||||
$COL_READ BOOLEAN NOT NULL,
|
||||
$COL_BOOKMARK BOOLEAN NOT NULL,
|
||||
$COL_LAST_PAGE_READ INT NOT NULL,
|
||||
$COL_PAGES_LEFT INT NOT NULL,
|
||||
$COL_CHAPTER_NUMBER FLOAT NOT NULL,
|
||||
$COL_SOURCE_ORDER INTEGER NOT NULL,
|
||||
$COL_DATE_FETCH LONG NOT NULL,
|
||||
$COL_DATE_UPLOAD LONG NOT NULL,
|
||||
FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID})
|
||||
ON DELETE CASCADE
|
||||
)"""
|
||||
|
||||
val createMangaIdIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_MANGA_ID}_index ON $TABLE($COL_MANGA_ID)"
|
||||
|
||||
val createUnreadChaptersIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " +
|
||||
"WHERE $COL_READ = 0"
|
||||
|
||||
val sourceOrderUpdateQuery: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0"
|
||||
|
||||
val bookmarkUpdateQuery: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_BOOKMARK BOOLEAN DEFAULT FALSE"
|
||||
|
||||
val addScanlator: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SCANLATOR TEXT DEFAULT NULL"
|
||||
|
||||
val pagesLeftQuery: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_PAGES_LEFT INTEGER DEFAULT 0"
|
||||
}
|
||||
|
|
|
@ -10,15 +10,4 @@ object MangaCategoryTable {
|
|||
|
||||
const val COL_CATEGORY_ID = "category_id"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_CATEGORY_ID INTEGER NOT NULL,
|
||||
FOREIGN KEY($COL_CATEGORY_ID) REFERENCES ${CategoryTable.TABLE} (${CategoryTable.COL_ID})
|
||||
ON DELETE CASCADE,
|
||||
FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID})
|
||||
ON DELETE CASCADE
|
||||
)"""
|
||||
}
|
||||
|
|
|
@ -50,47 +50,4 @@ object MangaTable {
|
|||
|
||||
const val COL_UPDATE_STRATEGY = "update_strategy"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_SOURCE INTEGER NOT NULL,
|
||||
$COL_URL TEXT NOT NULL,
|
||||
$COL_ARTIST TEXT,
|
||||
$COL_AUTHOR TEXT,
|
||||
$COL_DESCRIPTION TEXT,
|
||||
$COL_GENRE TEXT,
|
||||
$COL_TITLE TEXT NOT NULL,
|
||||
$COL_STATUS INTEGER NOT NULL,
|
||||
$COL_THUMBNAIL_URL TEXT,
|
||||
$COL_FAVORITE INTEGER NOT NULL,
|
||||
$COL_LAST_UPDATE LONG,
|
||||
$COL_INITIALIZED BOOLEAN NOT NULL,
|
||||
$COL_VIEWER INTEGER NOT NULL,
|
||||
$COL_HIDE_TITLE INTEGER NOT NULL,
|
||||
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
||||
$COL_DATE_ADDED LONG,
|
||||
$COL_FILTERED_SCANLATORS TEXT,
|
||||
$COL_UPDATE_STRATEGY INTEGER NOT NULL DEFAULT 0
|
||||
|
||||
)"""
|
||||
|
||||
val createUrlIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_URL}_index ON $TABLE($COL_URL)"
|
||||
|
||||
val createLibraryIndexQuery: String
|
||||
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
|
||||
"WHERE $COL_FAVORITE = 1"
|
||||
|
||||
val addHideTitle: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0"
|
||||
|
||||
val addDateAddedCol: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_DATE_ADDED LONG DEFAULT 0"
|
||||
|
||||
val addFilteredScanlators: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FILTERED_SCANLATORS TEXT"
|
||||
|
||||
val addUpdateStrategy: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_UPDATE_STRATEGY INTEGER NOT NULL DEFAULT 0"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
|
||||
object TrackTable {
|
||||
|
||||
const val TABLE = "manga_sync"
|
||||
|
@ -32,59 +30,4 @@ object TrackTable {
|
|||
|
||||
const val COL_FINISH_DATE = "finish_date"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_MANGA_ID INTEGER NOT NULL,
|
||||
$COL_SYNC_ID INTEGER NOT NULL,
|
||||
$COL_MEDIA_ID INTEGER NOT NULL,
|
||||
$COL_LIBRARY_ID INTEGER,
|
||||
$COL_TITLE TEXT NOT NULL,
|
||||
$COL_LAST_CHAPTER_READ REAL NOT NULL,
|
||||
$COL_TOTAL_CHAPTERS INTEGER NOT NULL,
|
||||
$COL_STATUS INTEGER NOT NULL,
|
||||
$COL_SCORE FLOAT NOT NULL,
|
||||
$COL_TRACKING_URL TEXT NOT NULL,
|
||||
$COL_START_DATE LONG NOT NULL,
|
||||
$COL_FINISH_DATE LONG NOT NULL,
|
||||
UNIQUE ($COL_MANGA_ID, $COL_SYNC_ID) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID})
|
||||
ON DELETE CASCADE
|
||||
)"""
|
||||
|
||||
val addTrackingUrl: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_TRACKING_URL TEXT DEFAULT ''"
|
||||
|
||||
val addLibraryId: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_LIBRARY_ID INTEGER NULL"
|
||||
|
||||
val addStartDate: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_START_DATE LONG NOT NULL DEFAULT 0"
|
||||
|
||||
val addFinishDate: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FINISH_DATE LONG NOT NULL DEFAULT 0"
|
||||
|
||||
val renameTableToTemp: String
|
||||
get() =
|
||||
"ALTER TABLE $TABLE RENAME TO ${TABLE}_tmp"
|
||||
|
||||
val insertFromTempTable: String
|
||||
get() =
|
||||
"""
|
||||
|INSERT INTO $TABLE($COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE)
|
||||
|SELECT $COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE
|
||||
|FROM ${TABLE}_tmp
|
||||
""".trimMargin()
|
||||
|
||||
val dropTempTable: String
|
||||
get() = "DROP TABLE ${TABLE}_tmp"
|
||||
|
||||
val updateMangaUpdatesScore: String
|
||||
get() =
|
||||
"""
|
||||
UPDATE $TABLE
|
||||
SET $COL_SCORE = max($COL_SCORE, 0)
|
||||
WHERE $COL_SYNC_ID = ${TrackManager.MANGA_UPDATES};
|
||||
""".trimIndent()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
package eu.kanade.tachiyomi.di
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
import dev.yokai.data.AndroidDatabaseHandler
|
||||
import dev.yokai.data.DatabaseHandler
|
||||
import dev.yokai.domain.SplashState
|
||||
import dev.yokai.domain.extension.TrustExtension
|
||||
import dev.yokai.domain.storage.StorageManager
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.DbOpenCallback
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
|
@ -18,10 +27,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterFilter
|
||||
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
|
||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
import kotlinx.serialization.json.Json
|
||||
import nl.adaptivity.xmlutil.XmlDeclMode
|
||||
import nl.adaptivity.xmlutil.core.XmlVersion
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import tachiyomi.data.Database
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addSingleton
|
||||
|
@ -33,7 +44,49 @@ class AppModule(val app: Application) : InjektModule {
|
|||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingleton(app)
|
||||
|
||||
addSingletonFactory { DatabaseHelper(app) }
|
||||
addSingletonFactory<SupportSQLiteOpenHelper> {
|
||||
val configuration = SupportSQLiteOpenHelper.Configuration.builder(app)
|
||||
.callback(DbOpenCallback())
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
.noBackupDirectory(false)
|
||||
.build()
|
||||
|
||||
/*
|
||||
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Support database inspector in Android Studio
|
||||
FrameworkSQLiteOpenHelperFactory().create(configuration)
|
||||
} else {
|
||||
RequerySQLiteOpenHelperFactory().create(configuration)
|
||||
}
|
||||
*/
|
||||
RequerySQLiteOpenHelperFactory().create(configuration)
|
||||
}
|
||||
|
||||
addSingletonFactory<SqlDriver> {
|
||||
AndroidSqliteDriver(openHelper = get())
|
||||
/*
|
||||
AndroidSqliteDriver(
|
||||
schema = Database.Schema,
|
||||
context = app,
|
||||
name = "tachiyomi.db",
|
||||
factory = if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Support database inspector in Android Studio
|
||||
FrameworkSQLiteOpenHelperFactory()
|
||||
} else {
|
||||
RequerySQLiteOpenHelperFactory()
|
||||
},
|
||||
callback = get<DbOpenCallback>(),
|
||||
)
|
||||
*/
|
||||
}
|
||||
addSingletonFactory {
|
||||
Database(
|
||||
driver = get(),
|
||||
)
|
||||
}
|
||||
addSingletonFactory<DatabaseHandler> { AndroidDatabaseHandler(get(), get()) }
|
||||
|
||||
addSingletonFactory { DatabaseHelper(app, get()) }
|
||||
|
||||
addSingletonFactory { ChapterCache(app) }
|
||||
|
||||
|
@ -87,6 +140,8 @@ class AppModule(val app: Application) : InjektModule {
|
|||
|
||||
get<SourceManager>()
|
||||
|
||||
get<Database>()
|
||||
|
||||
get<DatabaseHelper>()
|
||||
|
||||
get<DownloadManager>()
|
||||
|
|
7
app/src/main/sqldelight/tachiyomi/data/categories.sq
Normal file
7
app/src/main/sqldelight/tachiyomi/data/categories.sq
Normal file
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE categories(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL,
|
||||
flags INTEGER NOT NULL,
|
||||
manga_order TEXT NOT NULL
|
||||
);
|
24
app/src/main/sqldelight/tachiyomi/data/chapters.sq
Normal file
24
app/src/main/sqldelight/tachiyomi/data/chapters.sq
Normal file
|
@ -0,0 +1,24 @@
|
|||
import kotlin.Boolean;
|
||||
import kotlin.Float;
|
||||
import kotlin.Long;
|
||||
|
||||
CREATE TABLE chapters(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
manga_id INTEGER NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
scanlator TEXT,
|
||||
read INTEGER AS Boolean NOT NULL,
|
||||
bookmark INTEGER AS Boolean NOT NULL,
|
||||
last_page_read INTEGER NOT NULL,
|
||||
pages_left INTEGER NOT NULL,
|
||||
chapter_number REAL AS Float NOT NULL,
|
||||
source_order INTEGER NOT NULL,
|
||||
date_fetch INTEGER AS Long NOT NULL,
|
||||
date_upload INTEGER AS Long NOT NULL,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX chapters_manga_id_index ON chapters(manga_id);
|
||||
CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0;
|
12
app/src/main/sqldelight/tachiyomi/data/history.sq
Normal file
12
app/src/main/sqldelight/tachiyomi/data/history.sq
Normal file
|
@ -0,0 +1,12 @@
|
|||
import kotlin.Long;
|
||||
|
||||
CREATE TABLE history(
|
||||
history_id INTEGER NOT NULL PRIMARY KEY,
|
||||
history_chapter_id INTEGER NOT NULL UNIQUE,
|
||||
history_last_read INTEGER AS Long,
|
||||
history_time_read INTEGER AS Long,
|
||||
FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id);
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE mangas_categories(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
manga_id INTEGER NOT NULL,
|
||||
category_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(category_id) REFERENCES categories (_id)
|
||||
ON DELETE CASCADE,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
21
app/src/main/sqldelight/tachiyomi/data/manga_sync.sq
Normal file
21
app/src/main/sqldelight/tachiyomi/data/manga_sync.sq
Normal file
|
@ -0,0 +1,21 @@
|
|||
import kotlin.Float;
|
||||
import kotlin.Long;
|
||||
|
||||
CREATE TABLE manga_sync(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
manga_id INTEGER NOT NULL,
|
||||
sync_id INTEGER NOT NULL,
|
||||
remote_id INTEGER NOT NULL,
|
||||
library_id INTEGER,
|
||||
title TEXT NOT NULL,
|
||||
last_chapter_read REAL NOT NULL,
|
||||
total_chapters INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
score REAL AS Float NOT NULL,
|
||||
remote_url TEXT NOT NULL,
|
||||
start_date INTEGER AS Long NOT NULL,
|
||||
finish_date INTEGER AS Long NOT NULL,
|
||||
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
27
app/src/main/sqldelight/tachiyomi/data/mangas.sq
Normal file
27
app/src/main/sqldelight/tachiyomi/data/mangas.sq
Normal file
|
@ -0,0 +1,27 @@
|
|||
import kotlin.Boolean;
|
||||
import kotlin.Long;
|
||||
|
||||
CREATE TABLE mangas(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
source INTEGER NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
artist TEXT,
|
||||
author TEXT,
|
||||
description TEXT,
|
||||
genre TEXT,
|
||||
title TEXT NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
thumbnail_url TEXT,
|
||||
favorite INTEGER NOT NULL,
|
||||
last_update INTEGER AS Long,
|
||||
initialized INTEGER AS Boolean NOT NULL,
|
||||
viewer INTEGER NOT NULL,
|
||||
hideTitle INTEGER NOT NULL,
|
||||
chapter_flags INTEGER NOT NULL,
|
||||
date_added INTEGER AS Long,
|
||||
filtered_scanlators TEXT,
|
||||
update_strategy INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX mangas_url_index ON mangas(url);
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
3
app/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
3
app/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE chapters ADD COLUMN source_order INTEGER DEFAULT 0;
|
||||
|
||||
UPDATE mangas SET thumbnail_url = REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4;
|
1
app/src/main/sqldelight/tachiyomi/migrations/10.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/10.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE chapters ADD COLUMN pages_left INTEGER DEFAULT 0;
|
1
app/src/main/sqldelight/tachiyomi/migrations/11.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/11.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE mangas ADD COLUMN date_added INTEGER NOT NULL DEFAULT 0;
|
2
app/src/main/sqldelight/tachiyomi/migrations/12.sqm
Normal file
2
app/src/main/sqldelight/tachiyomi/migrations/12.sqm
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE manga_sync ADD COLUMN start_date INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE manga_sync ADD COLUMN finish_date INTEGER NOT NULL DEFAULT 0;
|
1
app/src/main/sqldelight/tachiyomi/migrations/13.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/13.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE mangas ADD COLUMN filtered_scanlators TEXT;
|
29
app/src/main/sqldelight/tachiyomi/migrations/14.sqm
Normal file
29
app/src/main/sqldelight/tachiyomi/migrations/14.sqm
Normal file
|
@ -0,0 +1,29 @@
|
|||
import kotlin.Float;
|
||||
import kotlin.Long;
|
||||
|
||||
ALTER TABLE manga_sync RENAME TO manga_sync_tmp;
|
||||
|
||||
CREATE TABLE manga_sync(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
manga_id INTEGER NOT NULL,
|
||||
sync_id INTEGER NOT NULL,
|
||||
remote_id INTEGER NOT NULL,
|
||||
library_id INTEGER,
|
||||
title TEXT NOT NULL,
|
||||
last_chapter_read REAL NOT NULL,
|
||||
total_chapters INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
score REAL AS Float NOT NULL,
|
||||
remote_url TEXT NOT NULL,
|
||||
start_date INTEGER AS Long NOT NULL,
|
||||
finish_date INTEGER AS Long NOT NULL,
|
||||
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO manga_sync(_id, manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date)
|
||||
SELECT _id,manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date
|
||||
FROM manga_sync_tmp;
|
||||
|
||||
DROP TABLE manga_sync_tmp;
|
1
app/src/main/sqldelight/tachiyomi/migrations/15.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/15.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE mangas ADD COLUMN update_strategy INTEGER NOT NULL DEFAULT 0;
|
3
app/src/main/sqldelight/tachiyomi/migrations/16.sqm
Normal file
3
app/src/main/sqldelight/tachiyomi/migrations/16.sqm
Normal file
|
@ -0,0 +1,3 @@
|
|||
UPDATE manga_sync
|
||||
SET score = max(score, 0)
|
||||
WHERE sync_id = 7;
|
10
app/src/main/sqldelight/tachiyomi/migrations/2.sqm
Normal file
10
app/src/main/sqldelight/tachiyomi/migrations/2.sqm
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE history(
|
||||
history_id INTEGER NOT NULL PRIMARY KEY,
|
||||
history_chapter_id INTEGER NOT NULL UNIQUE,
|
||||
history_last_read INTEGER,
|
||||
history_time_read INTEGER,
|
||||
FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id);
|
1
app/src/main/sqldelight/tachiyomi/migrations/3.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/3.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE chapters ADD COLUMN bookmark INTEGER DEFAULT 0;
|
1
app/src/main/sqldelight/tachiyomi/migrations/4.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/4.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE chapters ADD COLUMN scanlator TEXT DEFAULT NULL;
|
1
app/src/main/sqldelight/tachiyomi/migrations/5.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/5.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE manga_sync ADD COLUMN remote_url TEXT DEFAULT '';
|
1
app/src/main/sqldelight/tachiyomi/migrations/6.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/6.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE manga_sync ADD COLUMN library_id INTEGER;
|
5
app/src/main/sqldelight/tachiyomi/migrations/7.sqm
Normal file
5
app/src/main/sqldelight/tachiyomi/migrations/7.sqm
Normal file
|
@ -0,0 +1,5 @@
|
|||
DROP INDEX IF EXISTS mangas_favorite_index;
|
||||
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||
|
||||
CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0;
|
1
app/src/main/sqldelight/tachiyomi/migrations/8.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/8.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE mangas ADD COLUMN hideTitle INTEGER DEFAULT 0;
|
1
app/src/main/sqldelight/tachiyomi/migrations/9.sqm
Normal file
1
app/src/main/sqldelight/tachiyomi/migrations/9.sqm
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE categories ADD COLUMN manga_order TEXT;
|
|
@ -14,6 +14,7 @@ buildscript {
|
|||
classpath(libs.oss.licenses.plugin)
|
||||
classpath(kotlinx.serialization.gradle)
|
||||
classpath(libs.firebase.crashlytics.gradle)
|
||||
classpath(libs.sqldelight.gradle)
|
||||
}
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
|
|
|
@ -6,6 +6,8 @@ fast_adapter = "5.6.0"
|
|||
nucleus = "3.0.0"
|
||||
okhttp = "5.0.0-alpha.11"
|
||||
shizuku = "12.1.0"
|
||||
sqlite = "2.4.0"
|
||||
sqldelight = "2.0.2"
|
||||
junit = "5.8.2"
|
||||
|
||||
[libraries]
|
||||
|
@ -65,7 +67,17 @@ rxrelay = { module = "com.jakewharton.rxrelay:rxrelay", version = "1.2.0" }
|
|||
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
||||
rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" }
|
||||
slice = { module = "com.github.mthli:Slice", version = "v1.2" }
|
||||
sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.39.2" }
|
||||
|
||||
sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" }
|
||||
sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" }
|
||||
sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.45.0" }
|
||||
|
||||
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" }
|
||||
sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" }
|
||||
sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" }
|
||||
sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" }
|
||||
|
||||
subsamplingscaleimageview = { module = "com.github.null2264:subsampling-scale-image-view", version = "338caedb5f" }
|
||||
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
|
||||
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" }
|
||||
|
@ -82,7 +94,9 @@ gradle-versions = { id = "com.github.ben-manes.versions", version = "0.42.0" }
|
|||
|
||||
[bundles]
|
||||
archive = [ "common-compress", "junrar" ]
|
||||
db = [ "sqldelight-android-driver", "sqldelight-android-paging", "sqldelight-coroutines" ]
|
||||
coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ]
|
||||
sqlite = [ "sqlite-framework", "sqlite-ktx" ]
|
||||
test = [ "junit-api", "mockk" ]
|
||||
test-android = [ "junit-android" ]
|
||||
test-runtime = [ "junit-engine" ]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue