Update kotlin monorepo to v2.1.20 (mihonapp/mihon#1883)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit 556290f2d35f739bb4bddc012739acf10b92708d)
This commit is contained in:
parent
58a07df88d
commit
dd9f9a3447
7 changed files with 249 additions and 27 deletions
|
|
@ -102,9 +102,12 @@ import java.time.Instant
|
|||
import java.time.ZonedDateTime
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.concurrent.atomics.incrementAndFetch
|
||||
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
|
|
@ -376,7 +379,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
*/
|
||||
private suspend fun updateChapterList() {
|
||||
val semaphore = Semaphore(5)
|
||||
val progressCount = AtomicInteger(0)
|
||||
val progressCount = AtomicInt(0)
|
||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||
|
|
@ -441,7 +444,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
downloadChapters(manga, chaptersToDownload)
|
||||
hasDownloads.set(true)
|
||||
hasDownloads.store(true)
|
||||
}
|
||||
|
||||
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
|
||||
|
|
@ -477,7 +480,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
|
||||
if (newUpdates.isNotEmpty()) {
|
||||
notifier.showUpdateNotifications(newUpdates)
|
||||
if (hasDownloads.get()) {
|
||||
if (hasDownloads.load()) {
|
||||
downloadManager.startDownloads()
|
||||
}
|
||||
}
|
||||
|
|
@ -677,7 +680,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
|
||||
private suspend fun withUpdateNotification(
|
||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||
completed: AtomicInteger,
|
||||
completed: AtomicInt,
|
||||
manga: Manga,
|
||||
block: suspend () -> Unit,
|
||||
) = coroutineScope {
|
||||
|
|
@ -686,7 +689,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
updatingManga.add(manga)
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.get(),
|
||||
completed.load(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
|
||||
|
|
@ -695,10 +698,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
ensureActive()
|
||||
|
||||
updatingManga.remove(manga)
|
||||
completed.getAndIncrement()
|
||||
completed.incrementAndFetch()
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.get(),
|
||||
completed.load(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
package eu.kanade.tachiyomi.data.library
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkQuery
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.copyFrom
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.system.isRunning
|
||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.concurrent.atomics.fetchAndIncrement
|
||||
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
private val coverCache: CoverCache = Injekt.get()
|
||||
private val getLibraryManga: GetLibraryManga = Injekt.get()
|
||||
private val updateManga: UpdateManga = Injekt.get()
|
||||
|
||||
private val notifier = LibraryUpdateNotifier(context)
|
||||
|
||||
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
setForegroundSafely()
|
||||
|
||||
addMangaToQueue()
|
||||
|
||||
return withIOContext {
|
||||
try {
|
||||
updateMetadata()
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) {
|
||||
// Assume success although cancelled
|
||||
Result.success()
|
||||
} else {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.failure()
|
||||
}
|
||||
} finally {
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
val notifier = LibraryUpdateNotifier(context)
|
||||
return ForegroundInfo(
|
||||
Notifications.ID_LIBRARY_PROGRESS,
|
||||
notifier.progressNotificationBuilder.build(),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds list of manga to be updated.
|
||||
*/
|
||||
private suspend fun addMangaToQueue() {
|
||||
mangaToUpdate = getLibraryManga.await()
|
||||
notifier.showQueueSizeWarningNotificationIfNeeded(mangaToUpdate)
|
||||
}
|
||||
|
||||
private suspend fun updateMetadata() {
|
||||
val semaphore = Semaphore(5)
|
||||
val progressCount = AtomicInt(0)
|
||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||
|
||||
coroutineScope {
|
||||
mangaToUpdate.groupBy { it.manga.source }
|
||||
.values
|
||||
.map { mangaInSource ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
mangaInSource.forEach { libraryManga ->
|
||||
val manga = libraryManga.manga
|
||||
ensureActive()
|
||||
|
||||
withUpdateNotification(
|
||||
currentlyUpdatingManga,
|
||||
progressCount,
|
||||
manga,
|
||||
) {
|
||||
val source = sourceManager.get(manga.source) ?: return@withUpdateNotification
|
||||
try {
|
||||
val networkManga = source.getMangaDetails(manga.toSManga())
|
||||
val updatedManga = manga.prepUpdateCover(coverCache, networkManga, true)
|
||||
.copyFrom(networkManga)
|
||||
try {
|
||||
updateManga.await(updatedManga.toMangaUpdate())
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Ignore errors and continue
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
|
||||
private suspend fun withUpdateNotification(
|
||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||
completed: AtomicInt,
|
||||
manga: Manga,
|
||||
block: suspend () -> Unit,
|
||||
) = coroutineScope {
|
||||
ensureActive()
|
||||
|
||||
updatingManga.add(manga)
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.load(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
|
||||
block()
|
||||
|
||||
ensureActive()
|
||||
|
||||
updatingManga.remove(manga)
|
||||
completed.fetchAndIncrement()
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.load(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MetadataUpdate"
|
||||
private const val WORK_NAME_MANUAL = "MetadataUpdate"
|
||||
|
||||
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
|
||||
|
||||
fun startNow(context: Context): Boolean {
|
||||
val wm = context.workManager
|
||||
if (wm.isRunning(TAG)) {
|
||||
// Already running either as a scheduled or manual job
|
||||
return false
|
||||
}
|
||||
val request = OneTimeWorkRequestBuilder<MetadataUpdateJob>()
|
||||
.addTag(TAG)
|
||||
.addTag(WORK_NAME_MANUAL)
|
||||
.build()
|
||||
wm.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val wm = context.workManager
|
||||
val workQuery = WorkQuery.Builder.fromTags(listOf(TAG))
|
||||
.addStates(listOf(WorkInfo.State.RUNNING))
|
||||
.build()
|
||||
wm.getWorkInfos(workQuery).get()
|
||||
// Should only return one work but just in case
|
||||
.forEach {
|
||||
wm.cancelWorkById(it.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,16 +12,18 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
|
|||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.concurrent.atomics.AtomicReference
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
|
||||
/**
|
||||
* Base implementation class for extension installer. To be used inside a foreground [Service].
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
abstract class Installer(private val service: Service) {
|
||||
|
||||
private val extensionManager: ExtensionManager by injectLazy()
|
||||
|
||||
private var waitingInstall = AtomicReference<Entry>(null)
|
||||
private var waitingInstall = AtomicReference<Entry?>(null)
|
||||
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
||||
|
||||
private val cancelReceiver = object : BroadcastReceiver() {
|
||||
|
|
@ -79,7 +81,7 @@ abstract class Installer(private val service: Service) {
|
|||
* @see waitingInstall
|
||||
*/
|
||||
fun continueQueue(resultStep: InstallStep) {
|
||||
val completedEntry = waitingInstall.getAndSet(null)
|
||||
val completedEntry = waitingInstall.exchange(null)
|
||||
if (completedEntry != null) {
|
||||
extensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
||||
checkQueue()
|
||||
|
|
@ -115,10 +117,10 @@ abstract class Installer(private val service: Service) {
|
|||
LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver)
|
||||
queue.forEach { extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) }
|
||||
queue.clear()
|
||||
waitingInstall.set(null)
|
||||
waitingInstall.store(null)
|
||||
}
|
||||
|
||||
protected fun getActiveEntry(): Entry? = waitingInstall.get()
|
||||
protected fun getActiveEntry(): Entry? = waitingInstall.load()
|
||||
|
||||
/**
|
||||
* Cancels queue for the provided download ID if exists.
|
||||
|
|
@ -126,13 +128,13 @@ abstract class Installer(private val service: Service) {
|
|||
* @param downloadId Download ID as known by [ExtensionManager]
|
||||
*/
|
||||
private fun cancelQueue(downloadId: Long) {
|
||||
val waitingInstall = this.waitingInstall.get()
|
||||
val waitingInstall = this.waitingInstall.load()
|
||||
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
||||
if (cancelEntry(toCancel)) {
|
||||
queue.remove(toCancel)
|
||||
if (waitingInstall == toCancel) {
|
||||
// Currently processing removed entry, continue queue
|
||||
this.waitingInstall.set(null)
|
||||
this.waitingInstall.store(null)
|
||||
checkQueue()
|
||||
}
|
||||
extensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ import tachiyomi.core.common.util.lang.withIOContext
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.PriorityBlockingQueue
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.concurrent.atomics.incrementAndFetch
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
|
|
@ -246,15 +248,16 @@ internal class HttpPageLoader(
|
|||
/**
|
||||
* Data class used to keep ordering of pages in order to maintain priority.
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
private class PriorityPage(
|
||||
val page: ReaderPage,
|
||||
val priority: Int,
|
||||
) : Comparable<PriorityPage> {
|
||||
companion object {
|
||||
private val idGenerator = AtomicInteger()
|
||||
private val idGenerator = AtomicInt(0)
|
||||
}
|
||||
|
||||
private val identifier = idGenerator.incrementAndGet()
|
||||
private val identifier = idGenerator.incrementAndFetch()
|
||||
|
||||
override fun compareTo(other: PriorityPage): Int {
|
||||
val p = other.priority.compareTo(priority)
|
||||
|
|
|
|||
|
|
@ -16,20 +16,23 @@ import rx.Observable
|
|||
import rx.Producer
|
||||
import rx.Subscription
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
val jsonMime = "application/json; charset=utf-8".toMediaType()
|
||||
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
fun Call.asObservable(): Observable<Response> {
|
||||
return Observable.unsafeCreate { subscriber ->
|
||||
// Since Call is a one-shot type, clone it for each new subscriber.
|
||||
val call = clone()
|
||||
|
||||
// Wrap the call in a helper which handles both unsubscription and backpressure.
|
||||
val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
|
||||
val requestArbiter = object : Producer, Subscription {
|
||||
val boolean = AtomicBoolean(false)
|
||||
override fun request(n: Long) {
|
||||
if (n == 0L || !compareAndSet(false, true)) return
|
||||
if (n == 0L || !boolean.compareAndSet(expectedValue = false, newValue = true)) return
|
||||
|
||||
try {
|
||||
val response = call.execute()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ 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.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.concurrent.atomics.decrementAndFetch
|
||||
import kotlin.concurrent.atomics.incrementAndFetch
|
||||
import kotlin.coroutines.ContinuationInterceptor
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
|
@ -127,6 +130,7 @@ private suspend fun CoroutineDispatcher.acquireTransactionThread(
|
|||
/**
|
||||
* A [CoroutineContext.Element] that indicates there is an on-going database transaction.
|
||||
*/
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
private class TransactionElement(
|
||||
private val transactionThreadControlJob: Job,
|
||||
val transactionDispatcher: ContinuationInterceptor,
|
||||
|
|
@ -143,14 +147,14 @@ private class TransactionElement(
|
|||
* when [release] is invoked then the transaction job is cancelled and the transaction thread
|
||||
* is released.
|
||||
*/
|
||||
private val referenceCount = AtomicInteger(0)
|
||||
private val referenceCount = AtomicInt(0)
|
||||
|
||||
fun acquire() {
|
||||
referenceCount.incrementAndGet()
|
||||
referenceCount.incrementAndFetch()
|
||||
}
|
||||
|
||||
fun release() {
|
||||
val count = referenceCount.decrementAndGet()
|
||||
val count = referenceCount.decrementAndFetch()
|
||||
if (count < 0) {
|
||||
throw IllegalStateException("Transaction was never started or was already released")
|
||||
} else if (count == 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
kotlin_version = "2.1.0"
|
||||
kotlin_version = "2.1.20"
|
||||
serialization_version = "1.8.0"
|
||||
xml_serialization_version = "0.90.3"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue