Move tracker logic into own class

pull/178/head
Koitharu 4 years ago
parent 30c0fd600f
commit 3edfd0892a
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Android Studio default JDK" /> <option name="gradleJvm" value="Embedded JDK" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

@ -67,19 +67,19 @@ android {
afterEvaluate { afterEvaluate {
compileDebugKotlin { compileDebugKotlin {
kotlinOptions { kotlinOptions {
freeCompilerArgs += ['-opt-in=kotlin.RequiresOptIn'] freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
} }
} }
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:ab87a50e9b') { implementation('com.github.nv95:kotatsu-parsers:0ed35a4b21') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2'
implementation 'androidx.core:core-ktx:1.8.0-rc02' implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.activity:activity-ktx:1.5.0-rc01' implementation 'androidx.activity:activity-ktx:1.5.0-rc01'
implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01' implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc01' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc01'

@ -1,16 +1,14 @@
package org.koitharu.kotatsu.core.parser package org.koitharu.kotatsu.core.parser
import java.util.*
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import java.util.*
/** /**
* This parser is just for parser development, it should not be used in releases * This parser is just for parser development, it should not be used in releases
*/ */
@OptIn(InternalParsersApi::class)
class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) { class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain

@ -21,7 +21,7 @@ interface TrackLogsDao {
suspend fun removeAll(mangaId: Long) suspend fun removeAll(mangaId: Long)
@Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)") @Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)")
suspend fun cleanup() suspend fun gc()
@Query("SELECT COUNT(*) FROM track_logs") @Query("SELECT COUNT(*) FROM track_logs")
suspend fun count(): Int suspend fun count(): Int

@ -3,7 +3,6 @@ package org.koitharu.kotatsu.core.db.dao
import androidx.room.* import androidx.room.*
import org.koitharu.kotatsu.core.db.entity.TrackEntity import org.koitharu.kotatsu.core.db.entity.TrackEntity
@Dao @Dao
abstract class TracksDao { abstract class TracksDao {
@ -32,7 +31,7 @@ abstract class TracksDao {
abstract suspend fun delete(mangaId: Long) abstract suspend fun delete(mangaId: Long)
@Query("DELETE FROM tracks WHERE manga_id NOT IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)") @Query("DELETE FROM tracks WHERE manga_id NOT IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)")
abstract suspend fun cleanup() abstract suspend fun gc()
@Transaction @Transaction
open suspend fun upsert(entity: TrackEntity) { open suspend fun upsert(entity: TrackEntity) {

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.exceptions
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
class CompositeException(val errors: Collection<Throwable>) : Exception( class CompositeException(val errors: Collection<Throwable>) : Exception() {
message = errors.mapNotNullToSet { it.message }.joinToString()
) override val message: String = errors.mapNotNullToSet { it.message }.joinToString()
}

@ -1,14 +1,41 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import java.util.* import java.util.*
import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
data class MangaTracking( class MangaTracking(
val manga: Manga, val manga: Manga,
val knownChaptersCount: Int, val knownChaptersCount: Int,
val lastChapterId: Long, val lastChapterId: Long,
val lastNotifiedChapterId: Long, val lastNotifiedChapterId: Long,
val lastCheck: Date? val lastCheck: Date?,
) ) {
fun isEmpty(): Boolean {
return knownChaptersCount <= 0 || lastChapterId == 0L
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaTracking
if (manga != other.manga) return false
if (knownChaptersCount != other.knownChaptersCount) return false
if (lastChapterId != other.lastChapterId) return false
if (lastNotifiedChapterId != other.lastNotifiedChapterId) return false
if (lastCheck != other.lastCheck) return false
return true
}
override fun hashCode(): Int {
var result = manga.hashCode()
result = 31 * result + knownChaptersCount
result = 31 * result + lastChapterId.hashCode()
result = 31 * result + lastNotifiedChapterId.hashCode()
result = 31 * result + (lastCheck?.hashCode() ?: 0)
return result
}
}

@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser package org.koitharu.kotatsu.core.parser
import java.lang.ref.WeakReference
import java.util.*
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import java.lang.ref.WeakReference
import java.util.*
interface MangaRepository { interface MangaRepository {
@ -13,7 +13,7 @@ interface MangaRepository {
val sortOrders: Set<SortOrder> val sortOrders: Set<SortOrder>
suspend fun getList(offset: Int, query: String?): List<Manga> suspend fun getList(offset: Int, query: String): List<Manga>
suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga>

@ -20,7 +20,7 @@ class RemoteMangaRepository(private val parser: MangaParser) : MangaRepository {
getConfig().defaultSortOrder = value getConfig().defaultSortOrder = value
} }
override suspend fun getList(offset: Int, query: String?): List<Manga> { override suspend fun getList(offset: Int, query: String): List<Manga> {
return parser.getList(offset, query) return parser.getList(offset, query)
} }

@ -7,6 +7,12 @@ import androidx.annotation.WorkerThread
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import java.io.File
import java.io.IOException
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
@ -22,12 +28,6 @@ import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.readText import org.koitharu.kotatsu.utils.ext.readText
import org.koitharu.kotatsu.utils.ext.resolveName import org.koitharu.kotatsu.utils.ext.resolveName
import java.io.File
import java.io.IOException
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import kotlin.coroutines.CoroutineContext
private const val MAX_PARALLELISM = 4 private const val MAX_PARALLELISM = 4
@ -37,12 +37,12 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
private val filenameFilter = CbzFilter() private val filenameFilter = CbzFilter()
private val locks = CompositeMutex<Long>() private val locks = CompositeMutex<Long>()
override suspend fun getList(offset: Int, query: String?): List<Manga> { override suspend fun getList(offset: Int, query: String): List<Manga> {
if (offset > 0) { if (offset > 0) {
return emptyList() return emptyList()
} }
val list = getRawList() val list = getRawList()
if (!query.isNullOrEmpty()) { if (query.isNotEmpty()) {
list.retainAll { x -> list.retainAll { x ->
x.title.contains(query, ignoreCase = true) || x.title.contains(query, ignoreCase = true) ||
x.altTitle?.contains(query, ignoreCase = true) == true x.altTitle?.contains(query, ignoreCase = true) == true

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.tracker
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.ui.FeedViewModel import org.koitharu.kotatsu.tracker.ui.FeedViewModel
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
@ -13,5 +14,7 @@ val trackerModule
factory { TrackingRepository(get()) } factory { TrackingRepository(get()) }
factory { TrackerNotificationChannels(androidContext(), get()) } factory { TrackerNotificationChannels(androidContext(), get()) }
factory { Tracker(get()) }
viewModel { FeedViewModel(get()) } viewModel { FeedViewModel(get()) }
} }

@ -0,0 +1,132 @@
package org.koitharu.kotatsu.tracker.domain
import org.koitharu.kotatsu.core.model.MangaTracking
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
class Tracker(
private val repository: TrackingRepository,
) {
suspend fun fetchUpdates(track: MangaTracking, commit: Boolean): MangaUpdates {
val repo = MangaRepository(track.manga.source)
val details = repo.getDetails(track.manga)
val chapters = details.chapters.orEmpty()
if (track.isEmpty()) {
// first check or manga was empty on last check
if (commit) {
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = chapters.size,
lastChapterId = chapters.lastOrNull()?.id ?: 0L,
previousTrackChapterId = 0L,
newChapters = emptyList(),
saveTrackLog = false,
)
}
return MangaUpdates(
manga = details,
newChapters = emptyList(),
)
}
val newChapters = details.getNewChapters(track.lastChapterId)
if (newChapters.isEmpty()) {
if (commit) {
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = chapters.size,
lastChapterId = chapters.lastOrNull()?.id ?: 0L,
previousTrackChapterId = 0L,
newChapters = emptyList(),
saveTrackLog = false,
)
}
return MangaUpdates(
manga = details,
newChapters = emptyList(),
)
}
return when {
// the same chapters count
chapters.size == track.knownChaptersCount -> {
if (chapters.lastOrNull()?.id == track.lastChapterId) {
// manga was not updated. skip
MangaUpdates(
manga = details,
newChapters = emptyList(),
)
} else {
// number of chapters still the same, bu last chapter changed.
// maybe some chapters are removed. we need to find last known chapter
val knownChapter = chapters.indexOfLast { it.id == track.lastChapterId }
if (knownChapter == -1) {
// confuse. reset anything
if (commit) {
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = chapters.size,
lastChapterId = chapters.lastOrNull()?.id ?: 0L,
previousTrackChapterId = 0L,
newChapters = emptyList(),
saveTrackLog = false,
)
}
MangaUpdates(
manga = details,
newChapters = emptyList(),
)
} else {
val newChapters = chapters.takeLast(chapters.size - knownChapter + 1)
if (commit) {
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = knownChapter + 1,
lastChapterId = track.lastChapterId,
previousTrackChapterId = track.lastNotifiedChapterId,
newChapters = newChapters,
saveTrackLog = true,
)
}
MangaUpdates(
manga = details,
newChapters = details.getNewChapters(track.lastNotifiedChapterId),
)
}
}
}
else -> {
val newChapters = chapters.takeLast(chapters.size - track.knownChaptersCount)
if (commit) {
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = track.knownChaptersCount,
lastChapterId = track.lastChapterId,
previousTrackChapterId = track.lastNotifiedChapterId,
newChapters = newChapters,
saveTrackLog = true,
)
}
MangaUpdates(
manga = details,
newChapters = details.getNewChapters(track.lastNotifiedChapterId),
)
}
}
}
private fun Manga.getNewChapters(lastChapterId: Long): List<MangaChapter> {
val chapters = chapters ?: return emptyList()
if (lastChapterId == 0L) {
return emptyList()
}
val raw = chapters.takeLastWhile { x -> x.id != lastChapterId }
return if (raw.isEmpty() || raw.size == chapters.size) {
emptyList()
} else {
raw
}
}
}

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.tracker.domain package org.koitharu.kotatsu.tracker.domain
import androidx.room.withTransaction import androidx.room.withTransaction
import java.util.*
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
@ -11,7 +12,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import java.util.*
class TrackingRepository( class TrackingRepository(
private val db: MangaDatabase, private val db: MangaDatabase,
@ -28,7 +28,8 @@ class TrackingRepository(
suspend fun getFavouritesManga(): Map<FavouriteCategory, List<Manga>> { suspend fun getFavouritesManga(): Map<FavouriteCategory, List<Manga>> {
val categories = db.favouriteCategoriesDao.findAll() val categories = db.favouriteCategoriesDao.findAll()
return categories.associateTo(LinkedHashMap(categories.size)) { categoryEntity -> return categories.associateTo(LinkedHashMap(categories.size)) { categoryEntity ->
categoryEntity.toFavouriteCategory() to db.favouritesDao.findAllManga(categoryEntity.categoryId).toMangaList() categoryEntity.toFavouriteCategory() to db.favouritesDao.findAllManga(categoryEntity.categoryId)
.toMangaList()
} }
} }
@ -68,10 +69,10 @@ class TrackingRepository(
suspend fun clearLogs() = db.trackLogsDao.clear() suspend fun clearLogs() = db.trackLogsDao.clear()
suspend fun cleanup() { suspend fun gc() {
db.withTransaction { db.withTransaction {
db.tracksDao.cleanup() db.tracksDao.gc()
db.trackLogsDao.cleanup() db.trackLogsDao.gc()
} }
} }

@ -0,0 +1,9 @@
package org.koitharu.kotatsu.tracker.domain.model
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
class MangaUpdates(
val manga: Manga,
val newChapters: List<MangaChapter>,
)

@ -14,35 +14,38 @@ import androidx.lifecycle.map
import androidx.work.* import androidx.work.*
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.PendingIntentCompat import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.referer import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import org.koitharu.kotatsu.utils.ext.trySetForeground import org.koitharu.kotatsu.utils.ext.trySetForeground
import org.koitharu.kotatsu.utils.progress.Progress import org.koitharu.kotatsu.utils.progress.Progress
import java.util.concurrent.TimeUnit
class TrackWorker(context: Context, workerParams: WorkerParameters) : class TrackWorker(context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams), KoinComponent { CoroutineWorker(context, workerParams),
KoinComponent {
private val notificationManager by lazy { private val notificationManager by lazy {
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
} }
private val coil by inject<ImageLoader>() private val coil by inject<ImageLoader>()
private val repository by inject<TrackingRepository>() private val repository by inject<TrackingRepository>()
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
private val channels by inject<TrackerNotificationChannels>() private val channels by inject<TrackerNotificationChannels>()
private val tracker by inject<Tracker>()
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (!settings.isTrackerEnabled) { if (!settings.isTrackerEnabled) {
@ -54,84 +57,25 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
val tracks = getAllTracks() val tracks = getAllTracks()
var success = 0 var success = 0
val workData = Data.Builder() val workData = Data.Builder().putInt(DATA_TOTAL, tracks.size)
.putInt(DATA_TOTAL, tracks.size)
for ((index, item) in tracks.withIndex()) { for ((index, item) in tracks.withIndex()) {
val (track, channelId) = item val (track, channelId) = item
val details = runCatching { val updates = runCatching {
MangaRepository(track.manga.source).getDetails(track.manga) tracker.fetchUpdates(track, commit = true)
}.onSuccess {
success++
}.getOrNull() }.getOrNull()
workData.putInt(DATA_PROGRESS, index) workData.putInt(DATA_PROGRESS, index)
setProgress(workData.build()) setProgress(workData.build())
val chapters = details?.chapters ?: continue if (updates != null && updates.newChapters.isNotEmpty()) {
when { showNotification(
// first check or manga was empty on last check manga = updates.manga,
track.knownChaptersCount <= 0 || track.lastChapterId == 0L -> { channelId = channelId,
repository.storeTrackResult( newChapters = updates.newChapters,
mangaId = track.manga.id, )
knownChaptersCount = chapters.size,
lastChapterId = chapters.lastOrNull()?.id ?: 0L,
previousTrackChapterId = 0L,
newChapters = emptyList(),
saveTrackLog = false,
)
}
// the same chapters count
chapters.size == track.knownChaptersCount -> {
if (chapters.lastOrNull()?.id == track.lastChapterId) {
// manga was not updated. skip
} else {
// number of chapters still the same, bu last chapter changed.
// maybe some chapters are removed. we need to find last known chapter
val knownChapter = chapters.indexOfLast { it.id == track.lastChapterId }
if (knownChapter == -1) {
// confuse. reset anything
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = chapters.size,
lastChapterId = chapters.lastOrNull()?.id ?: 0L,
previousTrackChapterId = 0L,
newChapters = emptyList(),
saveTrackLog = false,
)
} else {
val newChapters = chapters.takeLast(chapters.size - knownChapter + 1)
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = knownChapter + 1,
lastChapterId = track.lastChapterId,
previousTrackChapterId = track.lastNotifiedChapterId,
newChapters = newChapters,
saveTrackLog = true,
)
showNotification(
details,
channelId,
newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId },
)
}
}
}
else -> {
val newChapters = chapters.takeLast(chapters.size - track.knownChaptersCount)
repository.storeTrackResult(
mangaId = track.manga.id,
knownChaptersCount = track.knownChaptersCount,
lastChapterId = track.lastChapterId,
previousTrackChapterId = track.lastNotifiedChapterId,
newChapters = newChapters,
saveTrackLog = true,
)
showNotification(
manga = track.manga,
channelId = channelId,
newChapters = newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId },
)
}
} }
success++
} }
repository.cleanup() repository.gc()
return if (success == 0) { return if (success == 0) {
Result.retry() Result.retry()
} else { } else {
@ -194,8 +138,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
val colorPrimary = ContextCompat.getColor(applicationContext, R.color.blue_primary) val colorPrimary = ContextCompat.getColor(applicationContext, R.color.blue_primary)
val builder = NotificationCompat.Builder(applicationContext, channelId) val builder = NotificationCompat.Builder(applicationContext, channelId)
val summary = applicationContext.resources.getQuantityString( val summary = applicationContext.resources.getQuantityString(
R.plurals.new_chapters, R.plurals.new_chapters, newChapters.size, newChapters.size
newChapters.size, newChapters.size
) )
with(builder) { with(builder) {
setContentText(summary) setContentText(summary)
@ -203,10 +146,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
setNumber(newChapters.size) setNumber(newChapters.size)
setLargeIcon( setLargeIcon(
coil.execute( coil.execute(
ImageRequest.Builder(applicationContext) ImageRequest.Builder(applicationContext).data(manga.coverUrl).referer(manga.publicUrl).build()
.data(manga.coverUrl)
.referer(manga.publicUrl)
.build()
).toBitmapOrNull() ).toBitmapOrNull()
) )
setSmallIcon(R.drawable.ic_stat_book_plus) setSmallIcon(R.drawable.ic_stat_book_plus)
@ -220,8 +160,10 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
val intent = DetailsActivity.newIntent(applicationContext, manga) val intent = DetailsActivity.newIntent(applicationContext, manga)
setContentIntent( setContentIntent(
PendingIntent.getActivity( PendingIntent.getActivity(
applicationContext, id, applicationContext,
intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE id,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
setAutoCancel(true) setAutoCancel(true)
@ -251,9 +193,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
val title = applicationContext.getString(R.string.check_for_new_chapters) val title = applicationContext.getString(R.string.check_for_new_chapters)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
WORKER_CHANNEL_ID, WORKER_CHANNEL_ID, title, NotificationManager.IMPORTANCE_LOW
title,
NotificationManager.IMPORTANCE_LOW
) )
channel.setShowBadge(false) channel.setShowBadge(false)
channel.enableVibration(false) channel.enableVibration(false)
@ -262,17 +202,11 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID) val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID).setContentTitle(title)
.setContentTitle(title) .setPriority(NotificationCompat.PRIORITY_MIN).setDefaults(0)
.setPriority(NotificationCompat.PRIORITY_MIN) .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)).setSilent(true)
.setDefaults(0) .setProgress(0, 0, true).setSmallIcon(android.R.drawable.stat_notify_sync)
.setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED).setOngoing(true).build()
.setSilent(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
.setOngoing(true)
.build()
return ForegroundInfo(WORKER_NOTIFICATION_ID, notification) return ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
} }
@ -287,44 +221,31 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
private const val TAG_ONESHOT = "tracking_oneshot" private const val TAG_ONESHOT = "tracking_oneshot"
fun setup(context: Context) { fun setup(context: Context) {
val constraints = Constraints.Builder() val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
.setRequiredNetworkType(NetworkType.CONNECTED) val request =
.build() PeriodicWorkRequestBuilder<TrackWorker>(4, TimeUnit.HOURS).setConstraints(constraints).addTag(TAG)
val request = PeriodicWorkRequestBuilder<TrackWorker>(4, TimeUnit.HOURS) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES).build()
.setConstraints(constraints) WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)
.addTag(TAG)
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)
} }
fun startNow(context: Context) { fun startNow(context: Context) {
val constraints = Constraints.Builder() val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
.setRequiredNetworkType(NetworkType.CONNECTED) val request = OneTimeWorkRequestBuilder<TrackWorker>().setConstraints(constraints).addTag(TAG_ONESHOT)
.build() .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).build()
val request = OneTimeWorkRequestBuilder<TrackWorker>() WorkManager.getInstance(context).enqueue(request)
.setConstraints(constraints)
.addTag(TAG_ONESHOT)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(context)
.enqueue(request)
} }
fun getProgressLiveData(context: Context): LiveData<Progress?> { fun getProgressLiveData(context: Context): LiveData<Progress?> {
return WorkManager.getInstance(context) return WorkManager.getInstance(context).getWorkInfosByTagLiveData(TAG).map { list ->
.getWorkInfosByTagLiveData(TAG) list.find { work ->
.map { list -> work.state == WorkInfo.State.RUNNING
list.find { work -> }?.let { workInfo ->
work.state == WorkInfo.State.RUNNING Progress(
}?.let { workInfo -> value = workInfo.progress.getInt(DATA_PROGRESS, 0),
Progress( total = workInfo.progress.getInt(DATA_TOTAL, -1)
value = workInfo.progress.getInt(DATA_PROGRESS, 0), ).takeUnless { it.isIndeterminate }
total = workInfo.progress.getInt(DATA_TOTAL, -1)
).takeUnless { it.isIndeterminate }
}
} }
}
} }
} }
} }

@ -5,7 +5,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.0' classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

Loading…
Cancel
Save