Compare commits

...

3 Commits

Author SHA1 Message Date
Koitharu
0b8afe9c40 Fix checking for new chapters in some cases (#1212, #1195, #1190) 2024-12-18 18:26:27 +02:00
Koitharu
754ccc4197 Added url for NoDataReceivedException 2024-12-18 16:26:49 +02:00
Koitharu
ef691b1aed Update parsers 2024-12-18 15:48:57 +02:00
16 changed files with 51 additions and 25 deletions

View File

@@ -18,8 +18,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 35
versionCode = 694
versionName = '7.7.2'
versionCode = 695
versionName = '7.7.3'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {

View File

@@ -3,5 +3,5 @@ package org.koitharu.kotatsu.core.exceptions
import okio.IOException
class NoDataReceivedException(
url: String,
val url: String,
) : IOException("No data has been received from $url")

View File

@@ -30,6 +30,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.find
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import java.io.File
import java.net.Proxy
@@ -412,10 +413,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getString(KEY_PROXY_PORT, null)?.toIntOrNull() ?: 0
val proxyLogin: String?
get() = prefs.getString(KEY_PROXY_LOGIN, null)?.takeUnless { it.isEmpty() }
get() = prefs.getString(KEY_PROXY_LOGIN, null)?.nullIfEmpty()
val proxyPassword: String?
get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeUnless { it.isEmpty() }
get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.nullIfEmpty()
var localListOrder: SortOrder
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)

View File

@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.settings.utils.validation.DomainValidator
class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {
@@ -38,7 +39,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)
is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue)
is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.takeUnless(String::isEmpty)
is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.nullIfEmpty()
} as T
}

View File

@@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Build
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.parsers.util.removeSuffix
import java.io.File
import java.lang.reflect.Array as ArrayReflect
@@ -80,7 +81,7 @@ private fun getVolumePathForAndroid11AndAbove(volumeId: String, context: Context
private fun getVolumeIdFromTreeUri(treeUri: Uri): String? {
val docId = DocumentsContract.getTreeDocumentId(treeUri)
val split = docId.split(":".toRegex())
return split.firstOrNull()?.takeUnless { it.isEmpty() }
return split.firstOrNull()?.nullIfEmpty()
}
private fun getDocumentPathFromTreeUri(treeUri: Uri): String? {

View File

@@ -142,6 +142,7 @@ fun Throwable.getCauseUrl(): String? = when (this) {
is NotFoundException -> url
is TooManyRequestExceptions -> url
is CaughtException -> cause?.getCauseUrl()
is NoDataReceivedException -> url
is CloudFlareBlockedException -> url
is CloudFlareProtectedException -> url
is HttpStatusException -> url

View File

@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import com.google.android.material.R as materialR
fun pageThumbnailAD(
@@ -36,7 +37,7 @@ fun pageThumbnailAD(
AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)
bind {
val data: Any = item.page.preview?.takeUnless { it.isEmpty() } ?: item.page.toMangaPage()
val data: Any = item.page.preview?.nullIfEmpty() ?: item.page.toMangaPage()
binding.imageViewThumb.newImageRequest(lifecycleOwner, data)?.run {
defaultPlaceholders(context)
size(thumbSize)

View File

@@ -36,6 +36,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.model.YEAR_MIN
import org.koitharu.kotatsu.parsers.util.ifZero
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
@@ -267,7 +268,7 @@ class FilterCoordinator @Inject constructor(
}
fun setQuery(value: String?) {
val newQuery = value?.trim()?.takeUnless { it.isEmpty() }
val newQuery = value?.trim()?.nullIfEmpty()
currentListFilter.update { oldValue ->
if (capabilities.isSearchWithFiltersSupported || newQuery == null) {
oldValue.copy(query = newQuery)

View File

@@ -10,6 +10,7 @@ import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
@CheckResult
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
@@ -34,7 +35,7 @@ private fun View.bindBadgeImpl(
if (counter > 0) {
badgeDrawable.number = counter
} else {
badgeDrawable.text = text?.takeUnless { it.isEmpty() }
badgeDrawable.text = text?.nullIfEmpty()
}
badgeDrawable.isVisible = true
badgeDrawable.align(this)

View File

@@ -38,6 +38,7 @@ import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -137,7 +138,7 @@ class ColorFilterConfigActivity :
}
private fun loadPreview(page: MangaPage) {
val data: Any = page.preview?.takeUnless { it.isEmpty() } ?: page
val data: Any = page.preview?.nullIfEmpty() ?: page
ImageRequest.Builder(this@ColorFilterConfigActivity)
.data(data)
.scale(Scale.FILL)

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.scrobbling.common.data
import android.content.Context
import androidx.core.content.edit
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
@@ -31,7 +32,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) {
ScrobblerUser(
id = lines[0].toLong(),
nickname = lines[1],
avatar = lines[2].takeUnless(String::isEmpty),
avatar = lines[2].nullIfEmpty(),
service = ScrobblerService.valueOf(lines[3]),
)
}

View File

@@ -7,6 +7,7 @@ import okhttp3.internal.closeQuietly
import okio.IOException
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
@@ -34,7 +35,7 @@ class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor {
}
if (response.mimeType?.toMediaTypeOrNull()?.subtype == SUBTYPE_HTML) {
val message = runCatchingCancellable {
response.parseHtml().title().takeUnless { it.isEmpty() }
response.parseHtml().title().nullIfEmpty()
}.onFailure {
response.closeQuietly()
}.getOrNull() ?: "Invalid response (${response.code})"

View File

@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.tracker.domain
import android.util.Log
import coil3.request.CachePolicy
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
@@ -11,6 +13,7 @@ import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.findById
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
@@ -45,8 +48,9 @@ class CheckNewChaptersUseCase @Inject constructor(
runCatchingCancellable {
repository.updateTracks()
val details = getFullManga(manga)
val chapters = details.chapters ?: return@withLock
val track = repository.getTrackOrNull(manga) ?: return@withLock
val branch = checkNotNull(details.chapters?.findById(currentChapterId)).branch
val chapters = details.getChapters(branch)
val chapterIndex = chapters.indexOfFirst { x -> x.id == currentChapterId }
val lastNewChapterIndex = chapters.size - track.newChapters
val lastChapter = chapters.lastOrNull()
@@ -70,7 +74,7 @@ class CheckNewChaptersUseCase @Inject constructor(
private suspend fun invokeImpl(track: MangaTracking): MangaUpdates = runCatchingCancellable {
val details = getFullManga(track.manga)
compare(track, details, getBranch(details))
compare(track, details, getBranch(details, track.lastChapterId))
}.getOrElse { error ->
MangaUpdates.Failure(
manga = track.manga,
@@ -80,9 +84,17 @@ class CheckNewChaptersUseCase @Inject constructor(
repository.saveUpdates(updates)
}
private suspend fun getBranch(manga: Manga): String? {
val history = historyRepository.getOne(manga)
return manga.getPreferredBranch(history)
private suspend fun getBranch(manga: Manga, trackChapterId: Long): String? {
historyRepository.getOne(manga)?.let {
manga.chapters?.findById(it.chapterId)
}?.let {
return it.branch
}
manga.chapters?.findById(trackChapterId)?.let {
return it.branch
}
// fallback
return manga.getPreferredBranch(null)
}
private suspend fun getFullManga(manga: Manga): Manga = when {
@@ -111,25 +123,29 @@ class CheckNewChaptersUseCase @Inject constructor(
private fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates.Success {
if (track.isEmpty()) {
// first check or manga was empty on last check
return MangaUpdates.Success(manga, emptyList(), isValid = false)
return MangaUpdates.Success(manga, branch, emptyList(), isValid = false)
}
val chapters = requireNotNull(manga.getChapters(branch))
if (BuildConfig.DEBUG && chapters.findById(track.lastChapterId) == null) {
Log.e("Tracker", "Chapter ${track.lastChapterId} not found")
}
val newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId }
return when {
newChapters.isEmpty() -> {
MangaUpdates.Success(
manga = manga,
branch = branch,
newChapters = emptyList(),
isValid = chapters.lastOrNull()?.id == track.lastChapterId,
)
}
newChapters.size == chapters.size -> {
MangaUpdates.Success(manga, emptyList(), isValid = false)
MangaUpdates.Success(manga, branch, emptyList(), isValid = false)
}
else -> {
MangaUpdates.Success(manga, newChapters, isValid = true)
MangaUpdates.Success(manga, branch, newChapters, isValid = true)
}
}
}

View File

@@ -216,7 +216,6 @@ class TrackingRepository @Inject constructor(
}
private fun TrackEntity.mergeWith(updates: MangaUpdates): TrackEntity {
val chapters = updates.manga.chapters.orEmpty()
return when (updates) {
is MangaUpdates.Failure -> TrackEntity(
mangaId = mangaId,
@@ -230,7 +229,7 @@ class TrackingRepository @Inject constructor(
is MangaUpdates.Success -> TrackEntity(
mangaId = mangaId,
lastChapterId = chapters.lastOrNull()?.id ?: NO_ID,
lastChapterId = updates.manga.getChapters(updates.branch).lastOrNull()?.id ?: NO_ID,
newChapters = if (updates.isValid) newChapters + updates.newChapters.size else 0,
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate },

View File

@@ -11,6 +11,7 @@ sealed interface MangaUpdates {
data class Success(
override val manga: Manga,
val branch: String?,
val newChapters: List<MangaChapter>,
val isValid: Boolean,
) : MangaUpdates {

View File

@@ -28,10 +28,10 @@ leakcanary = "3.0-alpha-8"
lifecycle = "2.8.7"
markwon = "4.6.2"
material = "1.12.0"
moshi = "1.15.1"
moshi = "1.15.2"
okhttp = "4.12.0"
okio = "3.9.1"
parsers = "fece09b781"
parsers = "f86d31f811"
preference = "1.2.1"
recyclerview = "1.3.2"
room = "2.6.1"