Refactor tracker

pull/450/head
Koitharu 3 years ago
parent fa0289eb27
commit 746eed698f
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -135,7 +135,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getEnumValue(KEY_ZOOM_MODE, ZoomMode.FIT_CENTER) get() = prefs.getEnumValue(KEY_ZOOM_MODE, ZoomMode.FIT_CENTER)
val trackSources: Set<String> val trackSources: Set<String>
get() = prefs.getStringSet(KEY_TRACK_SOURCES, null) ?: arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY) get() = prefs.getStringSet(KEY_TRACK_SOURCES, null) ?: setOf(TRACK_FAVOURITES)
var appPassword: String? var appPassword: String?
get() = prefs.getString(KEY_APP_PASSWORD, null) get() = prefs.getString(KEY_APP_PASSWORD, null)

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.util.ext package org.koitharu.kotatsu.core.util.ext
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
@ -13,6 +14,7 @@ import android.content.ContextWrapper
import android.content.OperationApplicationException import android.content.OperationApplicationException
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SyncResult import android.content.SyncResult
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.database.SQLException import android.database.SQLException
import android.graphics.Color import android.graphics.Color
@ -28,6 +30,8 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
@ -220,3 +224,9 @@ inline fun Activity.catchingWebViewUnavailability(block: () -> Unit): Boolean {
} }
} }
} }
fun Context.checkNotificationPermission(): Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
} else {
NotificationManagerCompat.from(this).areNotificationsEnabled()
}

@ -25,7 +25,7 @@ class Tracker @Inject constructor(
if (sources.isEmpty()) { if (sources.isEmpty()) {
return emptyList() return emptyList()
} }
val knownIds = HashSet<Manga>() val knownManga = HashSet<Long>()
val result = ArrayList<TrackingItem>() val result = ArrayList<TrackingItem>()
// Favourites // Favourites
if (AppSettings.TRACK_FAVOURITES in sources) { if (AppSettings.TRACK_FAVOURITES in sources) {
@ -42,7 +42,7 @@ class Tracker @Inject constructor(
null null
} }
for (track in categoryTracks) { for (track in categoryTracks) {
if (knownIds.add(track.manga)) { if (knownManga.add(track.manga.id)) {
result.add(TrackingItem(track, channelId)) result.add(TrackingItem(track, channelId))
} }
} }
@ -58,7 +58,7 @@ class Tracker @Inject constructor(
null null
} }
for (track in historyTracks) { for (track in historyTracks) {
if (knownIds.add(track.manga)) { if (knownManga.add(track.manga.id)) {
result.add(TrackingItem(track, channelId)) result.add(TrackingItem(track, channelId))
} }
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.tracker.work package org.koitharu.kotatsu.tracker.work
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
@ -34,11 +33,11 @@ import dagger.Reusable
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -49,6 +48,7 @@ import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.core.logs.TrackerLogger import org.koitharu.kotatsu.core.logs.TrackerLogger
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.trySetForeground import org.koitharu.kotatsu.core.util.ext.trySetForeground
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -70,6 +70,7 @@ class TrackWorker @AssistedInject constructor(
private val tracker: Tracker, private val tracker: Tracker,
@TrackerLogger private val logger: FileLogger, @TrackerLogger private val logger: FileLogger,
) : CoroutineWorker(context, workerParams) { ) : CoroutineWorker(context, workerParams) {
private val notificationManager by lazy { NotificationManagerCompat.from(applicationContext) } private val notificationManager by lazy { NotificationManagerCompat.from(applicationContext) }
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
@ -121,35 +122,36 @@ class TrackWorker @AssistedInject constructor(
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<MangaUpdates?> { private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<MangaUpdates?> {
val semaphore = Semaphore(MAX_PARALLELISM) val semaphore = Semaphore(MAX_PARALLELISM)
return supervisorScope { return channelFlow {
tracks.map { (track, channelId) -> for ((track, channelId) in tracks) {
async { launch {
semaphore.withPermit { semaphore.withPermit {
runCatchingCancellable { send(
tracker.fetchUpdates(track, commit = true) runCatchingCancellable {
}.onFailure { e -> tracker.fetchUpdates(track, commit = true)
if (e is CloudFlareProtectedException) { }.onFailure { e ->
CaptchaNotifier(applicationContext).notify(e) if (e is CloudFlareProtectedException) {
} CaptchaNotifier(applicationContext).notify(e)
logger.log("checkUpdatesAsync", e) }
}.onSuccess { updates -> logger.log("checkUpdatesAsync", e)
if (updates.isValid && updates.isNotEmpty()) { }.onSuccess { updates ->
showNotification( if (updates.isValid && updates.isNotEmpty()) {
manga = updates.manga, showNotification(
channelId = channelId, manga = updates.manga,
newChapters = updates.newChapters, channelId = channelId,
) newChapters = updates.newChapters,
} )
}.getOrNull() }
}.getOrNull(),
)
} }
} }
}.awaitAll() }
} }.toList(ArrayList(tracks.size))
} }
@SuppressLint("MissingPermission")
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) { private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {
if (newChapters.isEmpty() || channelId == null || !notificationManager.areNotificationsEnabled()) { if (newChapters.isEmpty() || channelId == null || !applicationContext.checkNotificationPermission()) {
return return
} }
val id = manga.url.hashCode() val id = manga.url.hashCode()

Loading…
Cancel
Save