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")
|
kotlin("plugin.serialization")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
id("com.google.android.gms.oss-licenses-plugin")
|
id("com.google.android.gms.oss-licenses-plugin")
|
||||||
|
id("app.cash.sqldelight")
|
||||||
id("com.google.gms.google-services") apply false
|
id("com.google.gms.google-services") apply false
|
||||||
id("com.google.firebase.crashlytics") apply false
|
id("com.google.firebase.crashlytics") apply false
|
||||||
}
|
}
|
||||||
|
@ -142,6 +143,16 @@ android {
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
namespace = "eu.kanade.tachiyomi"
|
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 {
|
dependencies {
|
||||||
|
@ -208,7 +219,9 @@ dependencies {
|
||||||
implementation(libs.play.services.gcm)
|
implementation(libs.play.services.gcm)
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
|
implementation(libs.bundles.db)
|
||||||
implementation(libs.sqlite.android)
|
implementation(libs.sqlite.android)
|
||||||
|
implementation(libs.bundles.sqlite)
|
||||||
//noinspection UseTomlInstead
|
//noinspection UseTomlInstead
|
||||||
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
|
||||||
//noinspection UseTomlInstead
|
//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 eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
val dateAdapter = object : ColumnAdapter<Date, Long> {
|
// TODO: Move to dev.yokai.data.DatabaseAdapter
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Int> {
|
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Int> {
|
||||||
private val enumValues by lazy { UpdateStrategy.entries }
|
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.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context) :
|
open class DatabaseHelper(
|
||||||
|
context: Context,
|
||||||
|
openHelper: SupportSQLiteOpenHelper,
|
||||||
|
) :
|
||||||
MangaQueries,
|
MangaQueries,
|
||||||
ChapterQueries,
|
ChapterQueries,
|
||||||
TrackQueries,
|
TrackQueries,
|
||||||
|
@ -38,13 +41,8 @@ open class DatabaseHelper(context: Context) :
|
||||||
HistoryQueries,
|
HistoryQueries,
|
||||||
SearchMetadataQueries {
|
SearchMetadataQueries {
|
||||||
|
|
||||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
|
||||||
.callback(DbOpenCallback())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override val db = DefaultStorIOSQLite.builder()
|
override val db = DefaultStorIOSQLite.builder()
|
||||||
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
|
.sqliteOpenHelper(openHelper)
|
||||||
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
.addTypeMapping(Manga::class.java, MangaTypeMapping())
|
||||||
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
|
||||||
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
.addTypeMapping(Track::class.java, TrackTypeMapping())
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
package eu.kanade.tachiyomi.data.database
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import tachiyomi.data.Database
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import timber.log.Timber
|
||||||
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
|
|
||||||
|
|
||||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
class DbOpenCallback : AndroidSqliteDriver.Callback(Database.Schema) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Name of the database file.
|
* Name of the database file.
|
||||||
*/
|
*/
|
||||||
const val DATABASE_NAME = "tachiyomi.db"
|
const val DATABASE_NAME = "tachiyomi.db"
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of the database.
|
|
||||||
*/
|
|
||||||
const val DATABASE_VERSION = 17
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
override fun onOpen(db: SupportSQLiteDatabase) {
|
||||||
|
@ -36,84 +27,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
cursor.close()
|
cursor.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||||
execSQL(MangaTable.createTableQuery)
|
Timber.d("Creating new database...")
|
||||||
execSQL(ChapterTable.createTableQuery)
|
super.onCreate(db)
|
||||||
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 onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
if (oldVersion < 2) {
|
if (oldVersion < newVersion) {
|
||||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
Timber.d("Upgrading database from $oldVersion to $newVersion")
|
||||||
|
super.onUpgrade(db, oldVersion, newVersion)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,16 +14,4 @@ object CategoryTable {
|
||||||
|
|
||||||
const val COL_MANGA_ORDER = "manga_order"
|
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"
|
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"
|
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"
|
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
|
package eu.kanade.tachiyomi.data.database.tables
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
|
|
||||||
object TrackTable {
|
object TrackTable {
|
||||||
|
|
||||||
const val TABLE = "manga_sync"
|
const val TABLE = "manga_sync"
|
||||||
|
@ -32,59 +30,4 @@ object TrackTable {
|
||||||
|
|
||||||
const val COL_FINISH_DATE = "finish_date"
|
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
|
package eu.kanade.tachiyomi.di
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.content.ContextCompat
|
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.SplashState
|
||||||
import dev.yokai.domain.extension.TrustExtension
|
import dev.yokai.domain.extension.TrustExtension
|
||||||
import dev.yokai.domain.storage.StorageManager
|
import dev.yokai.domain.storage.StorageManager
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider
|
import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
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.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
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.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterFilter
|
import eu.kanade.tachiyomi.util.chapter.ChapterFilter
|
||||||
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
|
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
|
||||||
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import nl.adaptivity.xmlutil.XmlDeclMode
|
import nl.adaptivity.xmlutil.XmlDeclMode
|
||||||
import nl.adaptivity.xmlutil.core.XmlVersion
|
import nl.adaptivity.xmlutil.core.XmlVersion
|
||||||
import nl.adaptivity.xmlutil.serialization.XML
|
import nl.adaptivity.xmlutil.serialization.XML
|
||||||
|
import tachiyomi.data.Database
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addSingleton
|
import uy.kohesive.injekt.api.addSingleton
|
||||||
|
@ -33,7 +44,49 @@ class AppModule(val app: Application) : InjektModule {
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
addSingleton(app)
|
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) }
|
addSingletonFactory { ChapterCache(app) }
|
||||||
|
|
||||||
|
@ -87,6 +140,8 @@ class AppModule(val app: Application) : InjektModule {
|
||||||
|
|
||||||
get<SourceManager>()
|
get<SourceManager>()
|
||||||
|
|
||||||
|
get<Database>()
|
||||||
|
|
||||||
get<DatabaseHelper>()
|
get<DatabaseHelper>()
|
||||||
|
|
||||||
get<DownloadManager>()
|
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(libs.oss.licenses.plugin)
|
||||||
classpath(kotlinx.serialization.gradle)
|
classpath(kotlinx.serialization.gradle)
|
||||||
classpath(libs.firebase.crashlytics.gradle)
|
classpath(libs.firebase.crashlytics.gradle)
|
||||||
|
classpath(libs.sqldelight.gradle)
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
|
|
@ -6,6 +6,8 @@ fast_adapter = "5.6.0"
|
||||||
nucleus = "3.0.0"
|
nucleus = "3.0.0"
|
||||||
okhttp = "5.0.0-alpha.11"
|
okhttp = "5.0.0-alpha.11"
|
||||||
shizuku = "12.1.0"
|
shizuku = "12.1.0"
|
||||||
|
sqlite = "2.4.0"
|
||||||
|
sqldelight = "2.0.2"
|
||||||
junit = "5.8.2"
|
junit = "5.8.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
@ -65,7 +67,17 @@ rxrelay = { module = "com.jakewharton.rxrelay:rxrelay", version = "1.2.0" }
|
||||||
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
||||||
rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" }
|
rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" }
|
||||||
slice = { module = "com.github.mthli:Slice", version = "v1.2" }
|
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" }
|
subsamplingscaleimageview = { module = "com.github.null2264:subsampling-scale-image-view", version = "338caedb5f" }
|
||||||
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
|
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
|
||||||
shizuku-provider = { module = "dev.rikka.shizuku:provider", 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]
|
[bundles]
|
||||||
archive = [ "common-compress", "junrar" ]
|
archive = [ "common-compress", "junrar" ]
|
||||||
|
db = [ "sqldelight-android-driver", "sqldelight-android-paging", "sqldelight-coroutines" ]
|
||||||
coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ]
|
coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ]
|
||||||
|
sqlite = [ "sqlite-framework", "sqlite-ktx" ]
|
||||||
test = [ "junit-api", "mockk" ]
|
test = [ "junit-api", "mockk" ]
|
||||||
test-android = [ "junit-android" ]
|
test-android = [ "junit-android" ]
|
||||||
test-runtime = [ "junit-engine" ]
|
test-runtime = [ "junit-engine" ]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue