mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-25 18:47:51 +02:00
Move SQLDelight to data module (#8954)
And use tachiyomi instead of eu.kanade.tachiyomi for package names in the module
This commit is contained in:
1
data/.gitignore
vendored
Normal file
1
data/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
27
data/build.gradle.kts
Normal file
27
data/build.gradle.kts
Normal file
@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
id("com.squareup.sqldelight")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "tachiyomi.data"
|
||||
|
||||
defaultConfig {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
database("Database") {
|
||||
packageName = "tachiyomi.data"
|
||||
dialect = "sqlite:3.24"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":source-api"))
|
||||
api(libs.sqldelight.android.driver)
|
||||
api(libs.sqldelight.coroutines)
|
||||
api(libs.sqldelight.android.paging)
|
||||
}
|
0
data/consumer-rules.pro
Normal file
0
data/consumer-rules.pro
Normal file
21
data/proguard-rules.pro
vendored
Normal file
21
data/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
4
data/src/main/AndroidManifest.xml
Normal file
4
data/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
89
data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt
Normal file
89
data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt
Normal file
@ -0,0 +1,89 @@
|
||||
package tachiyomi.data
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import com.squareup.sqldelight.Query
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToOne
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
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>,
|
||||
queryProvider: Database.(Long, Long) -> Query<T>,
|
||||
): PagingSource<Long, T> {
|
||||
return QueryPagingSource(
|
||||
handler = this,
|
||||
countQuery = countQuery,
|
||||
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) }
|
||||
}
|
||||
}
|
30
data/src/main/java/tachiyomi/data/DatabaseAdapter.kt
Normal file
30
data/src/main/java/tachiyomi/data/DatabaseAdapter.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package tachiyomi.data
|
||||
|
||||
import com.squareup.sqldelight.ColumnAdapter
|
||||
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)
|
||||
}
|
||||
|
||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Long> {
|
||||
private val enumValues by lazy { UpdateStrategy.values() }
|
||||
|
||||
override fun decode(databaseValue: Long): UpdateStrategy =
|
||||
enumValues.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
|
||||
|
||||
override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
|
||||
}
|
36
data/src/main/java/tachiyomi/data/DatabaseHandler.kt
Normal file
36
data/src/main/java/tachiyomi/data/DatabaseHandler.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package tachiyomi.data
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import com.squareup.sqldelight.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
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>,
|
||||
queryProvider: Database.(Long, Long) -> Query<T>,
|
||||
): PagingSource<Long, T>
|
||||
}
|
71
data/src/main/java/tachiyomi/data/QueryPagingSource.kt
Normal file
71
data/src/main/java/tachiyomi/data/QueryPagingSource.kt
Normal file
@ -0,0 +1,71 @@
|
||||
package tachiyomi.data
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.squareup.sqldelight.Query
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class QueryPagingSource<RowType : Any>(
|
||||
val handler: DatabaseHandler,
|
||||
val countQuery: Database.() -> Query<Long>,
|
||||
val queryProvider: Database.(Long, Long) -> Query<RowType>,
|
||||
) : PagingSource<Long, RowType>(), Query.Listener {
|
||||
|
||||
override val jumpingSupported: Boolean = true
|
||||
|
||||
private var currentQuery: Query<RowType>? by Delegates.observable(null) { _, old, new ->
|
||||
old?.removeListener(this)
|
||||
new?.addListener(this)
|
||||
}
|
||||
|
||||
init {
|
||||
registerInvalidatedCallback {
|
||||
currentQuery?.removeListener(this)
|
||||
currentQuery = null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, RowType> {
|
||||
try {
|
||||
val key = params.key ?: 0L
|
||||
val loadSize = params.loadSize
|
||||
val count = handler.awaitOne { countQuery() }
|
||||
|
||||
val (offset, limit) = when (params) {
|
||||
is LoadParams.Prepend -> key - loadSize to loadSize.toLong()
|
||||
else -> key to loadSize.toLong()
|
||||
}
|
||||
|
||||
val data = handler.awaitList {
|
||||
queryProvider(limit, offset)
|
||||
.also { currentQuery = it }
|
||||
}
|
||||
|
||||
val (prevKey, nextKey) = when (params) {
|
||||
is LoadParams.Append -> { offset - loadSize to offset + loadSize }
|
||||
else -> { offset to offset + loadSize }
|
||||
}
|
||||
|
||||
return LoadResult.Page(
|
||||
data = data,
|
||||
prevKey = if (offset <= 0L || prevKey < 0L) null else prevKey,
|
||||
nextKey = if (offset + loadSize >= count) null else nextKey,
|
||||
itemsBefore = maxOf(0L, offset).toInt(),
|
||||
itemsAfter = maxOf(0L, count - (offset + loadSize)).toInt(),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
return LoadResult.Error(throwable = e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Long, RowType>): Long? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
||||
}
|
||||
}
|
||||
|
||||
override fun queryResultsChanged() {
|
||||
invalidate()
|
||||
}
|
||||
}
|
161
data/src/main/java/tachiyomi/data/TransactionContext.kt
Normal file
161
data/src/main/java/tachiyomi/data/TransactionContext.kt
Normal file
@ -0,0 +1,161 @@
|
||||
package tachiyomi.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()
|
||||
}
|
||||
}
|
||||
}
|
65
data/src/main/sqldelight/tachiyomi/data/categories.sq
Normal file
65
data/src/main/sqldelight/tachiyomi/data/categories.sq
Normal file
@ -0,0 +1,65 @@
|
||||
CREATE TABLE categories(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL,
|
||||
flags INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Insert system category
|
||||
INSERT OR IGNORE INTO categories(_id, name, sort, flags) VALUES (0, "", -1, 0);
|
||||
-- Disallow deletion of default category
|
||||
CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE
|
||||
ON categories
|
||||
BEGIN SELECT CASE
|
||||
WHEN old._id <= 0 THEN
|
||||
RAISE(ABORT, "System category can't be deleted")
|
||||
END;
|
||||
END;
|
||||
|
||||
getCategory:
|
||||
SELECT *
|
||||
FROM categories
|
||||
WHERE _id = :id
|
||||
LIMIT 1;
|
||||
|
||||
getCategories:
|
||||
SELECT
|
||||
_id AS id,
|
||||
name,
|
||||
sort AS `order`,
|
||||
flags
|
||||
FROM categories
|
||||
ORDER BY sort;
|
||||
|
||||
getCategoriesByMangaId:
|
||||
SELECT
|
||||
C._id AS id,
|
||||
C.name,
|
||||
C.sort AS `order`,
|
||||
C.flags
|
||||
FROM categories C
|
||||
JOIN mangas_categories MC
|
||||
ON C._id = MC.category_id
|
||||
WHERE MC.manga_id = :mangaId;
|
||||
|
||||
insert:
|
||||
INSERT INTO categories(name, sort, flags)
|
||||
VALUES (:name, :order, :flags);
|
||||
|
||||
delete:
|
||||
DELETE FROM categories
|
||||
WHERE _id = :categoryId;
|
||||
|
||||
update:
|
||||
UPDATE categories
|
||||
SET name = coalesce(:name, name),
|
||||
sort = coalesce(:order, sort),
|
||||
flags = coalesce(:flags, flags)
|
||||
WHERE _id = :categoryId;
|
||||
|
||||
updateAllFlags:
|
||||
UPDATE categories SET
|
||||
flags = coalesce(?, flags);
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
72
data/src/main/sqldelight/tachiyomi/data/chapters.sq
Normal file
72
data/src/main/sqldelight/tachiyomi/data/chapters.sq
Normal file
@ -0,0 +1,72 @@
|
||||
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,
|
||||
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;
|
||||
|
||||
getChapterById:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE _id = :id;
|
||||
|
||||
getChaptersByMangaId:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE manga_id = :mangaId;
|
||||
|
||||
getBookmarkedChaptersByMangaId:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE bookmark
|
||||
AND manga_id = :mangaId;
|
||||
|
||||
getChapterByUrl:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE url = :chapterUrl;
|
||||
|
||||
getChapterByUrlAndMangaId:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE url = :chapterUrl
|
||||
AND manga_id = :mangaId;
|
||||
|
||||
removeChaptersWithIds:
|
||||
DELETE FROM chapters
|
||||
WHERE _id IN :chapterIds;
|
||||
|
||||
insert:
|
||||
INSERT INTO chapters(manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload)
|
||||
VALUES (:mangaId,:url,:name,:scanlator,:read,:bookmark,:lastPageRead,:chapterNumber,:sourceOrder,:dateFetch,:dateUpload);
|
||||
|
||||
update:
|
||||
UPDATE chapters
|
||||
SET manga_id = coalesce(:mangaId, manga_id),
|
||||
url = coalesce(:url, url),
|
||||
name = coalesce(:name, name),
|
||||
scanlator = coalesce(:scanlator, scanlator),
|
||||
read = coalesce(:read, read),
|
||||
bookmark = coalesce(:bookmark, bookmark),
|
||||
last_page_read = coalesce(:lastPageRead, last_page_read),
|
||||
chapter_number = coalesce(:chapterNumber, chapter_number),
|
||||
source_order = coalesce(:sourceOrder, source_order),
|
||||
date_fetch = coalesce(:dateFetch, date_fetch),
|
||||
date_upload = coalesce(:dateUpload, date_upload)
|
||||
WHERE _id = :chapterId;
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
73
data/src/main/sqldelight/tachiyomi/data/history.sq
Normal file
73
data/src/main/sqldelight/tachiyomi/data/history.sq
Normal file
@ -0,0 +1,73 @@
|
||||
import java.util.Date;
|
||||
|
||||
CREATE TABLE history(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
chapter_id INTEGER NOT NULL UNIQUE,
|
||||
last_read INTEGER AS Date,
|
||||
time_read INTEGER NOT NULL,
|
||||
FOREIGN KEY(chapter_id) REFERENCES chapters (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
||||
|
||||
getHistoryByMangaId:
|
||||
SELECT
|
||||
H._id,
|
||||
H.chapter_id,
|
||||
H.last_read,
|
||||
H.time_read
|
||||
FROM history H
|
||||
JOIN chapters C
|
||||
ON H.chapter_id = C._id
|
||||
WHERE C.manga_id = :mangaId AND C._id = H.chapter_id;
|
||||
|
||||
getHistoryByChapterUrl:
|
||||
SELECT
|
||||
H._id,
|
||||
H.chapter_id,
|
||||
H.last_read,
|
||||
H.time_read
|
||||
FROM history H
|
||||
JOIN chapters C
|
||||
ON H.chapter_id = C._id
|
||||
WHERE C.url = :chapterUrl AND C._id = H.chapter_id;
|
||||
|
||||
resetHistoryById:
|
||||
UPDATE history
|
||||
SET last_read = 0
|
||||
WHERE _id = :historyId;
|
||||
|
||||
resetHistoryByMangaId:
|
||||
UPDATE history
|
||||
SET last_read = 0
|
||||
WHERE _id IN (
|
||||
SELECT H._id
|
||||
FROM mangas M
|
||||
INNER JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
INNER JOIN history H
|
||||
ON C._id = H.chapter_id
|
||||
WHERE M._id = :mangaId
|
||||
);
|
||||
|
||||
removeAllHistory:
|
||||
DELETE FROM history;
|
||||
|
||||
removeResettedHistory:
|
||||
DELETE FROM history
|
||||
WHERE last_read = 0;
|
||||
|
||||
upsert:
|
||||
INSERT INTO history(chapter_id, last_read, time_read)
|
||||
VALUES (:chapterId, :readAt, :time_read)
|
||||
ON CONFLICT(chapter_id)
|
||||
DO UPDATE
|
||||
SET
|
||||
last_read = :readAt,
|
||||
time_read = time_read + :time_read
|
||||
WHERE chapter_id = :chapterId;
|
||||
|
||||
getReadDuration:
|
||||
SELECT coalesce(sum(time_read), 0)
|
||||
FROM history;
|
57
data/src/main/sqldelight/tachiyomi/data/manga_sync.sq
Normal file
57
data/src/main/sqldelight/tachiyomi/data/manga_sync.sq
Normal file
@ -0,0 +1,57 @@
|
||||
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
|
||||
);
|
||||
|
||||
delete:
|
||||
DELETE FROM manga_sync
|
||||
WHERE manga_id = :mangaId AND sync_id = :syncId;
|
||||
|
||||
getTracks:
|
||||
SELECT *
|
||||
FROM manga_sync;
|
||||
|
||||
getTrackById:
|
||||
SELECT *
|
||||
FROM manga_sync
|
||||
WHERE _id = :id;
|
||||
|
||||
getTracksByMangaId:
|
||||
SELECT *
|
||||
FROM manga_sync
|
||||
WHERE manga_id = :mangaId;
|
||||
|
||||
insert:
|
||||
INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date)
|
||||
VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate);
|
||||
|
||||
update:
|
||||
UPDATE manga_sync
|
||||
SET
|
||||
manga_id = coalesce(:mangaId, manga_id),
|
||||
sync_id = coalesce(:syncId, sync_id),
|
||||
remote_id = coalesce(:mediaId, remote_id),
|
||||
library_id = coalesce(:libraryId, library_id),
|
||||
title = coalesce(:title, title),
|
||||
last_chapter_read = coalesce(:lastChapterRead, last_chapter_read),
|
||||
total_chapters = coalesce(:totalChapter, total_chapters),
|
||||
status = coalesce(:status, status),
|
||||
score = coalesce(:score, score),
|
||||
remote_url = coalesce(:trackingUrl, remote_url),
|
||||
start_date = coalesce(:startDate, start_date),
|
||||
finish_date = coalesce(:finishDate, finish_date)
|
||||
WHERE _id = :id;
|
108
data/src/main/sqldelight/tachiyomi/data/mangas.sq
Normal file
108
data/src/main/sqldelight/tachiyomi/data/mangas.sq
Normal file
@ -0,0 +1,108 @@
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy;
|
||||
import java.lang.String;
|
||||
import kotlin.collections.List;
|
||||
|
||||
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 AS List<String>,
|
||||
title TEXT NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
thumbnail_url TEXT,
|
||||
favorite INTEGER AS Boolean NOT NULL,
|
||||
last_update INTEGER AS Long,
|
||||
next_update INTEGER AS Long,
|
||||
initialized INTEGER AS Boolean NOT NULL,
|
||||
viewer INTEGER NOT NULL,
|
||||
chapter_flags INTEGER NOT NULL,
|
||||
cover_last_modified INTEGER AS Long NOT NULL,
|
||||
date_added INTEGER AS Long NOT NULL,
|
||||
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||
CREATE INDEX mangas_url_index ON mangas(url);
|
||||
|
||||
getMangaById:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE _id = :id;
|
||||
|
||||
-- TODO: this should ideally never really have more than 1 result
|
||||
getMangaByUrlAndSource:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE url = :url AND source = :source
|
||||
LIMIT 1;
|
||||
|
||||
getFavorites:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE favorite = 1;
|
||||
|
||||
getSourceIdWithFavoriteCount:
|
||||
SELECT
|
||||
source,
|
||||
count(*)
|
||||
FROM mangas
|
||||
WHERE favorite = 1
|
||||
GROUP BY source;
|
||||
|
||||
getFavoriteBySourceId:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE favorite = 1
|
||||
AND source = :sourceId;
|
||||
|
||||
getDuplicateLibraryManga:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE favorite = 1
|
||||
AND LOWER(title) = :title
|
||||
LIMIT 1;
|
||||
|
||||
resetViewerFlags:
|
||||
UPDATE mangas
|
||||
SET viewer = 0;
|
||||
|
||||
getSourceIdsWithNonLibraryManga:
|
||||
SELECT source, COUNT(*) AS manga_count
|
||||
FROM mangas
|
||||
WHERE favorite = 0
|
||||
GROUP BY source;
|
||||
|
||||
deleteMangasNotInLibraryBySourceIds:
|
||||
DELETE FROM mangas
|
||||
WHERE favorite = 0 AND source IN :sourceIds;
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added,update_strategy)
|
||||
VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnailUrl,:favorite,:lastUpdate,:nextUpdate,:initialized,:viewerFlags,:chapterFlags,:coverLastModified,:dateAdded,:updateStrategy);
|
||||
|
||||
update:
|
||||
UPDATE mangas SET
|
||||
source = coalesce(:source, source),
|
||||
url = coalesce(:url, url),
|
||||
artist = coalesce(:artist, artist),
|
||||
author = coalesce(:author, author),
|
||||
description = coalesce(:description, description),
|
||||
genre = coalesce(:genre, genre),
|
||||
title = coalesce(:title, title),
|
||||
status = coalesce(:status, status),
|
||||
thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url),
|
||||
favorite = coalesce(:favorite, favorite),
|
||||
last_update = coalesce(:lastUpdate, last_update),
|
||||
initialized = coalesce(:initialized, initialized),
|
||||
viewer = coalesce(:viewer, viewer),
|
||||
chapter_flags = coalesce(:chapterFlags, chapter_flags),
|
||||
cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
|
||||
date_added = coalesce(:dateAdded, date_added),
|
||||
update_strategy = coalesce(:updateStrategy, update_strategy)
|
||||
WHERE _id = :mangaId;
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
17
data/src/main/sqldelight/tachiyomi/data/mangas_categories.sq
Normal file
17
data/src/main/sqldelight/tachiyomi/data/mangas_categories.sq
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
||||
);
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas_categories(manga_id, category_id)
|
||||
VALUES (:mangaId, :categoryId);
|
||||
|
||||
deleteMangaCategoryByMangaId:
|
||||
DELETE FROM mangas_categories
|
||||
WHERE manga_id = :mangaId;
|
24
data/src/main/sqldelight/tachiyomi/data/sources.sq
Normal file
24
data/src/main/sqldelight/tachiyomi/data/sources.sq
Normal file
@ -0,0 +1,24 @@
|
||||
CREATE TABLE sources(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
lang TEXT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
findAll:
|
||||
SELECT *
|
||||
FROM sources;
|
||||
|
||||
findOne:
|
||||
SELECT *
|
||||
FROM sources
|
||||
WHERE _id = :id;
|
||||
|
||||
upsert:
|
||||
INSERT INTO sources(_id, lang, name)
|
||||
VALUES (:id, :lang, :name)
|
||||
ON CONFLICT(_id)
|
||||
DO UPDATE
|
||||
SET
|
||||
lang = :lang,
|
||||
name = :name
|
||||
WHERE _id = :id;
|
6
data/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
6
data/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
@ -0,0 +1,6 @@
|
||||
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;
|
11
data/src/main/sqldelight/tachiyomi/migrations/10.sqm
Normal file
11
data/src/main/sqldelight/tachiyomi/migrations/10.sqm
Normal file
@ -0,0 +1,11 @@
|
||||
ALTER TABLE mangas
|
||||
ADD COLUMN date_added INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE mangas
|
||||
SET date_added = (
|
||||
SELECT MIN(date_fetch)
|
||||
FROM mangas M
|
||||
INNER JOIN chapters C
|
||||
ON M._id = C.manga_id
|
||||
GROUP BY M._id
|
||||
);
|
2
data/src/main/sqldelight/tachiyomi/migrations/11.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/11.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE mangas
|
||||
ADD COLUMN next_update INTEGER DEFAULT 0;
|
27
data/src/main/sqldelight/tachiyomi/migrations/12.sqm
Normal file
27
data/src/main/sqldelight/tachiyomi/migrations/12.sqm
Normal file
@ -0,0 +1,27 @@
|
||||
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;
|
3
data/src/main/sqldelight/tachiyomi/migrations/13.sqm
Normal file
3
data/src/main/sqldelight/tachiyomi/migrations/13.sqm
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE chapters
|
||||
SET date_upload = date_fetch
|
||||
WHERE date_upload = 0;
|
149
data/src/main/sqldelight/tachiyomi/migrations/14.sqm
Normal file
149
data/src/main/sqldelight/tachiyomi/migrations/14.sqm
Normal file
@ -0,0 +1,149 @@
|
||||
DROP INDEX IF EXISTS chapters_manga_id_index;
|
||||
DROP INDEX IF EXISTS chapters_unread_by_manga_index;
|
||||
DROP INDEX IF EXISTS history_history_chapter_id_index;
|
||||
DROP INDEX IF EXISTS library_favorite_index;
|
||||
DROP INDEX IF EXISTS mangas_url_index;
|
||||
|
||||
ALTER TABLE mangas RENAME TO manga_temp;
|
||||
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,
|
||||
next_update INTEGER AS Long,
|
||||
initialized INTEGER AS Boolean NOT NULL,
|
||||
viewer INTEGER NOT NULL,
|
||||
chapter_flags INTEGER NOT NULL,
|
||||
cover_last_modified INTEGER AS Long NOT NULL,
|
||||
date_added INTEGER AS Long NOT NULL
|
||||
);
|
||||
INSERT INTO mangas
|
||||
SELECT _id,source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added
|
||||
FROM manga_temp;
|
||||
|
||||
ALTER TABLE categories RENAME TO categories_temp;
|
||||
CREATE TABLE categories(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL,
|
||||
flags INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO categories
|
||||
SELECT _id,name,sort,flags
|
||||
FROM categories_temp;
|
||||
|
||||
ALTER TABLE chapters RENAME TO chapters_temp;
|
||||
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,
|
||||
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
|
||||
);
|
||||
INSERT INTO chapters
|
||||
SELECT _id,manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload
|
||||
FROM chapters_temp;
|
||||
|
||||
ALTER TABLE history RENAME TO history_temp;
|
||||
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
|
||||
);
|
||||
INSERT INTO history
|
||||
SELECT history_id, history_chapter_id, history_last_read, history_time_read
|
||||
FROM history_temp;
|
||||
|
||||
ALTER TABLE mangas_categories RENAME TO mangas_categories_temp;
|
||||
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
|
||||
);
|
||||
INSERT INTO mangas_categories
|
||||
SELECT _id, manga_id, category_id
|
||||
FROM mangas_categories_temp;
|
||||
|
||||
ALTER TABLE manga_sync RENAME TO manga_sync_temp;
|
||||
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
|
||||
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_temp;
|
||||
|
||||
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;
|
||||
CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id);
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||
CREATE INDEX mangas_url_index ON mangas(url);
|
||||
|
||||
CREATE VIEW IF NOT EXISTS historyView AS
|
||||
SELECT
|
||||
history.history_id AS id,
|
||||
mangas._id AS mangaId,
|
||||
chapters._id AS chapterId,
|
||||
mangas.title,
|
||||
mangas.thumbnail_url AS thumnailUrl,
|
||||
chapters.chapter_number AS chapterNumber,
|
||||
history.history_last_read AS readAt,
|
||||
max_last_read.history_last_read AS maxReadAt,
|
||||
max_last_read.history_chapter_id AS maxReadAtChapterId
|
||||
FROM mangas
|
||||
JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
JOIN history
|
||||
ON chapters._id = history.history_chapter_id
|
||||
JOIN (
|
||||
SELECT chapters.manga_id,chapters._id AS history_chapter_id, MAX(history.history_last_read) AS history_last_read
|
||||
FROM chapters JOIN history
|
||||
ON chapters._id = history.history_chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS max_last_read
|
||||
ON chapters.manga_id = max_last_read.manga_id;
|
||||
|
||||
DROP TABLE IF EXISTS manga_sync_temp;
|
||||
DROP TABLE IF EXISTS mangas_categories_temp;
|
||||
DROP TABLE IF EXISTS history_temp;
|
||||
DROP TABLE IF EXISTS chapters_temp;
|
||||
DROP TABLE IF EXISTS categories_temp;
|
||||
DROP TABLE IF EXISTS manga_temp;
|
52
data/src/main/sqldelight/tachiyomi/migrations/15.sqm
Normal file
52
data/src/main/sqldelight/tachiyomi/migrations/15.sqm
Normal file
@ -0,0 +1,52 @@
|
||||
import java.util.Date;
|
||||
|
||||
DROP INDEX IF EXISTS history_history_chapter_id_index;
|
||||
DROP VIEW IF EXISTS historyView;
|
||||
|
||||
/**
|
||||
* [last_read] was made not-null
|
||||
* [time_read] was kept as long and made non-null
|
||||
* `history` prefix was removed from table name
|
||||
*/
|
||||
ALTER TABLE history RENAME TO history_temp;
|
||||
CREATE TABLE history(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
chapter_id INTEGER NOT NULL UNIQUE,
|
||||
last_read INTEGER AS Date NOT NULL,
|
||||
time_read INTEGER NOT NULL,
|
||||
FOREIGN KEY(chapter_id) REFERENCES chapters (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
INSERT INTO history
|
||||
SELECT history_id, history_chapter_id, coalesce(history_last_read, 0), coalesce(history_time_read, 0)
|
||||
FROM history_temp;
|
||||
|
||||
/**
|
||||
* [history.time_read] was added as a column in [historyView]
|
||||
*/
|
||||
CREATE VIEW historyView AS
|
||||
SELECT
|
||||
history._id AS id,
|
||||
mangas._id AS mangaId,
|
||||
chapters._id AS chapterId,
|
||||
mangas.title,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
chapters.chapter_number AS chapterNumber,
|
||||
history.last_read AS readAt,
|
||||
history.time_read AS readDuration,
|
||||
max_last_read.last_read AS maxReadAt,
|
||||
max_last_read.chapter_id AS maxReadAtChapterId
|
||||
FROM mangas
|
||||
JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
JOIN (
|
||||
SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read
|
||||
FROM chapters JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS max_last_read
|
||||
ON chapters.manga_id = max_last_read.manga_id;
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
5
data/src/main/sqldelight/tachiyomi/migrations/16.sqm
Normal file
5
data/src/main/sqldelight/tachiyomi/migrations/16.sqm
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE sources(
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
lang TEXT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
29
data/src/main/sqldelight/tachiyomi/migrations/17.sqm
Normal file
29
data/src/main/sqldelight/tachiyomi/migrations/17.sqm
Normal file
@ -0,0 +1,29 @@
|
||||
DROP VIEW IF EXISTS historyView;
|
||||
|
||||
CREATE VIEW historyView AS
|
||||
SELECT
|
||||
history._id AS id,
|
||||
mangas._id AS mangaId,
|
||||
chapters._id AS chapterId,
|
||||
mangas.title,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.cover_last_modified,
|
||||
chapters.chapter_number AS chapterNumber,
|
||||
history.last_read AS readAt,
|
||||
history.time_read AS readDuration,
|
||||
max_last_read.last_read AS maxReadAt,
|
||||
max_last_read.chapter_id AS maxReadAtChapterId
|
||||
FROM mangas
|
||||
JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
JOIN (
|
||||
SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read
|
||||
FROM chapters JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS max_last_read
|
||||
ON chapters.manga_id = max_last_read.manga_id;
|
20
data/src/main/sqldelight/tachiyomi/migrations/18.sqm
Normal file
20
data/src/main/sqldelight/tachiyomi/migrations/18.sqm
Normal file
@ -0,0 +1,20 @@
|
||||
CREATE VIEW updatesView AS
|
||||
SELECT
|
||||
mangas._id AS mangaId,
|
||||
mangas.title AS mangaTitle,
|
||||
chapters._id AS chapterId,
|
||||
chapters.name AS chapterName,
|
||||
chapters.scanlator,
|
||||
chapters.read,
|
||||
chapters.bookmark,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
mangas.cover_last_modified AS coverLastModified,
|
||||
chapters.date_upload AS dateUpload,
|
||||
chapters.date_fetch AS datefetch
|
||||
FROM mangas JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
WHERE favorite = 1
|
||||
AND date_fetch > date_added
|
||||
ORDER BY date_fetch DESC;
|
10
data/src/main/sqldelight/tachiyomi/migrations/19.sqm
Normal file
10
data/src/main/sqldelight/tachiyomi/migrations/19.sqm
Normal file
@ -0,0 +1,10 @@
|
||||
-- Insert Default category
|
||||
INSERT OR IGNORE INTO categories(_id, name, sort, flags) VALUES (0, "", -1, 0);
|
||||
-- Disallow deletion of default category
|
||||
CREATE TRIGGER IF NOT EXISTS system_category_delete_trigger BEFORE DELETE
|
||||
ON categories
|
||||
BEGIN SELECT CASE
|
||||
WHEN old._id <= 0 THEN
|
||||
RAISE(ABORT, "System category can't be deleted")
|
||||
END;
|
||||
END;
|
10
data/src/main/sqldelight/tachiyomi/migrations/2.sqm
Normal file
10
data/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
data/src/main/sqldelight/tachiyomi/migrations/20.sqm
Normal file
1
data/src/main/sqldelight/tachiyomi/migrations/20.sqm
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE mangas ADD COLUMN update_strategy INTEGER NOT NULL DEFAULT 0;
|
27
data/src/main/sqldelight/tachiyomi/migrations/21.sqm
Normal file
27
data/src/main/sqldelight/tachiyomi/migrations/21.sqm
Normal file
@ -0,0 +1,27 @@
|
||||
CREATE VIEW libraryView AS
|
||||
SELECT
|
||||
M.*,
|
||||
coalesce(C.total - C.readCount, 0) AS unreadCount,
|
||||
coalesce(C.readCount, 0) AS readCount,
|
||||
coalesce(C.latestUpload, 0) AS latestUpload,
|
||||
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
|
||||
coalesce(C.lastRead, 0) AS lastRead,
|
||||
COALESCE(MC.category_id, 0) AS category
|
||||
FROM mangas M
|
||||
LEFT JOIN mangas_categories AS MC
|
||||
ON MC.manga_id = M._id
|
||||
LEFT JOIN(
|
||||
SELECT
|
||||
chapters.manga_id,
|
||||
count(*) AS total,
|
||||
sum(read) AS readCount,
|
||||
max(chapters.date_upload) AS latestUpload,
|
||||
max(history.last_read) AS lastRead,
|
||||
max(chapters.date_fetch) AS fetchedAt
|
||||
FROM chapters
|
||||
LEFT JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS C
|
||||
ON M._id = C.manga_id
|
||||
WHERE M.favorite = 1;
|
31
data/src/main/sqldelight/tachiyomi/migrations/22.sqm
Normal file
31
data/src/main/sqldelight/tachiyomi/migrations/22.sqm
Normal file
@ -0,0 +1,31 @@
|
||||
DROP VIEW libraryView;
|
||||
|
||||
CREATE VIEW libraryView AS
|
||||
SELECT
|
||||
M.*,
|
||||
coalesce(C.total, 0) AS totalCount,
|
||||
coalesce(C.readCount, 0) AS readCount,
|
||||
coalesce(C.latestUpload, 0) AS latestUpload,
|
||||
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
|
||||
coalesce(C.lastRead, 0) AS lastRead,
|
||||
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
|
||||
coalesce(MC.category_id, 0) AS category
|
||||
FROM mangas M
|
||||
LEFT JOIN(
|
||||
SELECT
|
||||
chapters.manga_id,
|
||||
count(*) AS total,
|
||||
sum(read) AS readCount,
|
||||
coalesce(max(chapters.date_upload), 0) AS latestUpload,
|
||||
coalesce(max(history.last_read), 0) AS lastRead,
|
||||
coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
|
||||
sum(chapters.bookmark) AS bookmarkCount
|
||||
FROM chapters
|
||||
LEFT JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS C
|
||||
ON M._id = C.manga_id
|
||||
LEFT JOIN mangas_categories AS MC
|
||||
ON MC.manga_id = M._id
|
||||
WHERE M.favorite = 1;
|
23
data/src/main/sqldelight/tachiyomi/migrations/23.sqm
Normal file
23
data/src/main/sqldelight/tachiyomi/migrations/23.sqm
Normal file
@ -0,0 +1,23 @@
|
||||
DROP VIEW IF EXISTS updatesView;
|
||||
|
||||
CREATE VIEW updatesView AS
|
||||
SELECT
|
||||
mangas._id AS mangaId,
|
||||
mangas.title AS mangaTitle,
|
||||
chapters._id AS chapterId,
|
||||
chapters.name AS chapterName,
|
||||
chapters.scanlator,
|
||||
chapters.read,
|
||||
chapters.bookmark,
|
||||
chapters.last_page_read,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
mangas.cover_last_modified AS coverLastModified,
|
||||
chapters.date_upload AS dateUpload,
|
||||
chapters.date_fetch AS datefetch
|
||||
FROM mangas JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
WHERE favorite = 1
|
||||
AND date_fetch > date_added
|
||||
ORDER BY date_fetch DESC;
|
2
data/src/main/sqldelight/tachiyomi/migrations/3.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/3.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE chapters
|
||||
ADD COLUMN bookmark INTEGER DEFAULT 0;
|
2
data/src/main/sqldelight/tachiyomi/migrations/4.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/4.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE chapters
|
||||
ADD COLUMN scanlator TEXT DEFAULT NULL;
|
2
data/src/main/sqldelight/tachiyomi/migrations/5.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/5.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE manga_sync
|
||||
ADD COLUMN remote_url TEXT DEFAULT '';
|
2
data/src/main/sqldelight/tachiyomi/migrations/6.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/6.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE manga_sync
|
||||
ADD COLUMN library_id INTEGER;
|
9
data/src/main/sqldelight/tachiyomi/migrations/7.sqm
Normal file
9
data/src/main/sqldelight/tachiyomi/migrations/7.sqm
Normal file
@ -0,0 +1,9 @@
|
||||
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;
|
5
data/src/main/sqldelight/tachiyomi/migrations/8.sqm
Normal file
5
data/src/main/sqldelight/tachiyomi/migrations/8.sqm
Normal file
@ -0,0 +1,5 @@
|
||||
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;
|
2
data/src/main/sqldelight/tachiyomi/migrations/9.sqm
Normal file
2
data/src/main/sqldelight/tachiyomi/migrations/9.sqm
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE mangas
|
||||
ADD COLUMN cover_last_modified INTEGER NOT NULL DEFAULT 0;
|
64
data/src/main/sqldelight/tachiyomi/view/historyView.sq
Normal file
64
data/src/main/sqldelight/tachiyomi/view/historyView.sq
Normal file
@ -0,0 +1,64 @@
|
||||
CREATE VIEW historyView AS
|
||||
SELECT
|
||||
history._id AS id,
|
||||
mangas._id AS mangaId,
|
||||
chapters._id AS chapterId,
|
||||
mangas.title,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.cover_last_modified,
|
||||
chapters.chapter_number AS chapterNumber,
|
||||
history.last_read AS readAt,
|
||||
history.time_read AS readDuration,
|
||||
max_last_read.last_read AS maxReadAt,
|
||||
max_last_read.chapter_id AS maxReadAtChapterId
|
||||
FROM mangas
|
||||
JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
JOIN (
|
||||
SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read
|
||||
FROM chapters JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS max_last_read
|
||||
ON chapters.manga_id = max_last_read.manga_id;
|
||||
|
||||
history:
|
||||
SELECT
|
||||
id,
|
||||
mangaId,
|
||||
chapterId,
|
||||
title,
|
||||
thumbnailUrl,
|
||||
source,
|
||||
favorite,
|
||||
cover_last_modified,
|
||||
chapterNumber,
|
||||
readAt,
|
||||
readDuration
|
||||
FROM historyView
|
||||
WHERE historyView.readAt > 0
|
||||
AND maxReadAtChapterId = historyView.chapterId
|
||||
AND lower(historyView.title) LIKE ('%' || :query || '%')
|
||||
ORDER BY readAt DESC;
|
||||
|
||||
getLatestHistory:
|
||||
SELECT
|
||||
id,
|
||||
mangaId,
|
||||
chapterId,
|
||||
title,
|
||||
thumbnailUrl,
|
||||
source,
|
||||
favorite,
|
||||
cover_last_modified,
|
||||
chapterNumber,
|
||||
readAt,
|
||||
readDuration
|
||||
FROM historyView
|
||||
WHERE historyView.readAt > 0
|
||||
ORDER BY readAt DESC
|
||||
LIMIT 1;
|
33
data/src/main/sqldelight/tachiyomi/view/libraryView.sq
Normal file
33
data/src/main/sqldelight/tachiyomi/view/libraryView.sq
Normal file
@ -0,0 +1,33 @@
|
||||
CREATE VIEW libraryView AS
|
||||
SELECT
|
||||
M.*,
|
||||
coalesce(C.total, 0) AS totalCount,
|
||||
coalesce(C.readCount, 0) AS readCount,
|
||||
coalesce(C.latestUpload, 0) AS latestUpload,
|
||||
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
|
||||
coalesce(C.lastRead, 0) AS lastRead,
|
||||
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
|
||||
coalesce(MC.category_id, 0) AS category
|
||||
FROM mangas M
|
||||
LEFT JOIN(
|
||||
SELECT
|
||||
chapters.manga_id,
|
||||
count(*) AS total,
|
||||
sum(read) AS readCount,
|
||||
coalesce(max(chapters.date_upload), 0) AS latestUpload,
|
||||
coalesce(max(history.last_read), 0) AS lastRead,
|
||||
coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
|
||||
sum(chapters.bookmark) AS bookmarkCount
|
||||
FROM chapters
|
||||
LEFT JOIN history
|
||||
ON chapters._id = history.chapter_id
|
||||
GROUP BY chapters.manga_id
|
||||
) AS C
|
||||
ON M._id = C.manga_id
|
||||
LEFT JOIN mangas_categories AS MC
|
||||
ON MC.manga_id = M._id
|
||||
WHERE M.favorite = 1;
|
||||
|
||||
library:
|
||||
SELECT *
|
||||
FROM libraryView;
|
26
data/src/main/sqldelight/tachiyomi/view/updatesView.sq
Normal file
26
data/src/main/sqldelight/tachiyomi/view/updatesView.sq
Normal file
@ -0,0 +1,26 @@
|
||||
CREATE VIEW updatesView AS
|
||||
SELECT
|
||||
mangas._id AS mangaId,
|
||||
mangas.title AS mangaTitle,
|
||||
chapters._id AS chapterId,
|
||||
chapters.name AS chapterName,
|
||||
chapters.scanlator,
|
||||
chapters.read,
|
||||
chapters.bookmark,
|
||||
chapters.last_page_read,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
mangas.cover_last_modified AS coverLastModified,
|
||||
chapters.date_upload AS dateUpload,
|
||||
chapters.date_fetch AS datefetch
|
||||
FROM mangas JOIN chapters
|
||||
ON mangas._id = chapters.manga_id
|
||||
WHERE favorite = 1
|
||||
AND date_fetch > date_added
|
||||
ORDER BY date_fetch DESC;
|
||||
|
||||
updates:
|
||||
SELECT *
|
||||
FROM updatesView
|
||||
WHERE dateUpload > :after;
|
Reference in New Issue
Block a user