mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Revert "Revert history Compose/SQLDelight changes"
This reverts commit 96c894ce5b.
			
			
This commit is contained in:
		
							
								
								
									
										94
									
								
								app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package eu.kanade.data
 | 
			
		||||
 | 
			
		||||
import androidx.paging.PagingSource
 | 
			
		||||
import com.squareup.sqldelight.Query
 | 
			
		||||
import com.squareup.sqldelight.Transacter
 | 
			
		||||
import com.squareup.sqldelight.android.paging3.QueryPagingSource
 | 
			
		||||
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 eu.kanade.tachiyomi.Database
 | 
			
		||||
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>,
 | 
			
		||||
        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/eu/kanade/data/DatabaseAdapter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/src/main/java/eu/kanade/data/DatabaseAdapter.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package eu.kanade.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.sqldelight.ColumnAdapter
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
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/eu/kanade/data/DatabaseHandler.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/src/main/java/eu/kanade/data/DatabaseHandler.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
package eu.kanade.data
 | 
			
		||||
 | 
			
		||||
import androidx.paging.PagingSource
 | 
			
		||||
import com.squareup.sqldelight.Query
 | 
			
		||||
import com.squareup.sqldelight.Transacter
 | 
			
		||||
import eu.kanade.tachiyomi.Database
 | 
			
		||||
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>,
 | 
			
		||||
        transacter: Database.() -> Transacter,
 | 
			
		||||
        queryProvider: Database.(Long, Long) -> Query<T>
 | 
			
		||||
    ): PagingSource<Long, T>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										160
									
								
								app/src/main/java/eu/kanade/data/TransactionContext.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								app/src/main/java/eu/kanade/data/TransactionContext.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
package eu.kanade.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()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package eu.kanade.data.chapter
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.chapter.model.Chapter
 | 
			
		||||
 | 
			
		||||
val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Chapter =
 | 
			
		||||
    { id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
 | 
			
		||||
        Chapter(
 | 
			
		||||
            id = id,
 | 
			
		||||
            mangaId = mangaId,
 | 
			
		||||
            read = read,
 | 
			
		||||
            bookmark = bookmark,
 | 
			
		||||
            lastPageRead = lastPageRead,
 | 
			
		||||
            dateFetch = dateFetch,
 | 
			
		||||
            sourceOrder = sourceOrder,
 | 
			
		||||
            url = url,
 | 
			
		||||
            name = name,
 | 
			
		||||
            dateUpload = dateUpload,
 | 
			
		||||
            chapterNumber = chapterNumber,
 | 
			
		||||
            scanlator = scanlator,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										26
									
								
								app/src/main/java/eu/kanade/data/history/HistoryMapper.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/src/main/java/eu/kanade/data/history/HistoryMapper.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package eu.kanade.data.history
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.history.model.History
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryWithRelations
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
val historyMapper: (Long, Long, Date?, Date?) -> History = { id, chapterId, readAt, _ ->
 | 
			
		||||
    History(
 | 
			
		||||
        id = id,
 | 
			
		||||
        chapterId = chapterId,
 | 
			
		||||
        readAt = readAt,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val historyWithRelationsMapper: (Long, Long, Long, String, String?, Float, Date?) -> HistoryWithRelations = {
 | 
			
		||||
    historyId, mangaId, chapterId, title, thumbnailUrl, chapterNumber, readAt ->
 | 
			
		||||
    HistoryWithRelations(
 | 
			
		||||
        id = historyId,
 | 
			
		||||
        chapterId = chapterId,
 | 
			
		||||
        mangaId = mangaId,
 | 
			
		||||
        title = title,
 | 
			
		||||
        thumbnailUrl = thumbnailUrl ?: "",
 | 
			
		||||
        chapterNumber = chapterNumber,
 | 
			
		||||
        readAt = readAt
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,91 @@
 | 
			
		||||
package eu.kanade.data.history
 | 
			
		||||
 | 
			
		||||
import androidx.paging.PagingSource
 | 
			
		||||
import eu.kanade.data.DatabaseHandler
 | 
			
		||||
import eu.kanade.data.chapter.chapterMapper
 | 
			
		||||
import eu.kanade.data.manga.mangaMapper
 | 
			
		||||
import eu.kanade.domain.chapter.model.Chapter
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryWithRelations
 | 
			
		||||
import eu.kanade.domain.history.repository.HistoryRepository
 | 
			
		||||
import eu.kanade.domain.manga.model.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
 | 
			
		||||
class HistoryRepositoryImpl(
 | 
			
		||||
    private val handler: DatabaseHandler
 | 
			
		||||
) : HistoryRepository {
 | 
			
		||||
 | 
			
		||||
    override fun getHistory(query: String): PagingSource<Long, HistoryWithRelations> {
 | 
			
		||||
        return handler.subscribeToPagingSource(
 | 
			
		||||
            countQuery = { historyViewQueries.countHistory(query) },
 | 
			
		||||
            transacter = { historyViewQueries },
 | 
			
		||||
            queryProvider = { limit, offset ->
 | 
			
		||||
                historyViewQueries.history(query, limit, offset, historyWithRelationsMapper)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getNextChapterForManga(mangaId: Long, chapterId: Long): Chapter? {
 | 
			
		||||
        val chapter = handler.awaitOne { chaptersQueries.getChapterById(chapterId, chapterMapper) }
 | 
			
		||||
        val manga = handler.awaitOne { mangasQueries.getMangaById(mangaId, mangaMapper) }
 | 
			
		||||
 | 
			
		||||
        if (!chapter.read) {
 | 
			
		||||
            return chapter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
 | 
			
		||||
            Manga.CHAPTER_SORTING_SOURCE -> { c1, c2 -> c2.sourceOrder.compareTo(c1.sourceOrder) }
 | 
			
		||||
            Manga.CHAPTER_SORTING_NUMBER -> { c1, c2 -> c1.chapterNumber.compareTo(c2.chapterNumber) }
 | 
			
		||||
            Manga.CHAPTER_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.dateUpload.compareTo(c2.dateUpload) }
 | 
			
		||||
            else -> throw NotImplementedError("Unknown sorting method")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val chapters = handler.awaitList { chaptersQueries.getChapterByMangaId(mangaId, chapterMapper) }
 | 
			
		||||
            .sortedWith(sortFunction)
 | 
			
		||||
 | 
			
		||||
        val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
 | 
			
		||||
        return when (manga.sorting) {
 | 
			
		||||
            Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
 | 
			
		||||
            Manga.CHAPTER_SORTING_NUMBER -> {
 | 
			
		||||
                val chapterNumber = chapter.chapterNumber
 | 
			
		||||
 | 
			
		||||
                ((currChapterIndex + 1) until chapters.size)
 | 
			
		||||
                    .map { chapters[it] }
 | 
			
		||||
                    .firstOrNull {
 | 
			
		||||
                        it.chapterNumber > chapterNumber &&
 | 
			
		||||
                            it.chapterNumber <= chapterNumber + 1
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
            Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
 | 
			
		||||
                chapters.drop(currChapterIndex + 1)
 | 
			
		||||
                    .firstOrNull { it.dateUpload >= chapter.dateUpload }
 | 
			
		||||
            }
 | 
			
		||||
            else -> throw NotImplementedError("Unknown sorting method")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun resetHistory(historyId: Long) {
 | 
			
		||||
        try {
 | 
			
		||||
            handler.await { historyQueries.resetHistoryById(historyId) }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            logcat(throwable = e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun resetHistoryByMangaId(mangaId: Long) {
 | 
			
		||||
        try {
 | 
			
		||||
            handler.await { historyQueries.resetHistoryByMangaId(mangaId) }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            logcat(throwable = e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun deleteAllHistory(): Boolean {
 | 
			
		||||
        return try {
 | 
			
		||||
            handler.await { historyQueries.removeAllHistory() }
 | 
			
		||||
            true
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            logcat(throwable = e)
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								app/src/main/java/eu/kanade/data/manga/MangaMapper.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/src/main/java/eu/kanade/data/manga/MangaMapper.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package eu.kanade.data.manga
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.manga.model.Manga
 | 
			
		||||
 | 
			
		||||
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Manga =
 | 
			
		||||
    { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded ->
 | 
			
		||||
        Manga(
 | 
			
		||||
            id = id,
 | 
			
		||||
            source = source,
 | 
			
		||||
            favorite = favorite,
 | 
			
		||||
            lastUpdate = lastUpdate ?: 0,
 | 
			
		||||
            dateAdded = dateAdded,
 | 
			
		||||
            viewerFlags = viewer,
 | 
			
		||||
            chapterFlags = chapterFlags,
 | 
			
		||||
            coverLastModified = coverLastModified,
 | 
			
		||||
            url = url,
 | 
			
		||||
            title = title,
 | 
			
		||||
            artist = artist,
 | 
			
		||||
            author = author,
 | 
			
		||||
            description = description,
 | 
			
		||||
            genre = genre,
 | 
			
		||||
            status = status,
 | 
			
		||||
            thumbnailUrl = thumbnailUrl,
 | 
			
		||||
            initialized = initialized,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
		Reference in New Issue
	
	Block a user