mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-25 04:20:40 +02:00
@@ -16,11 +16,9 @@ class ReorderCategory(
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun moveUp(category: Category): Result =
|
||||
await(category, MoveTo.UP)
|
||||
suspend fun moveUp(category: Category): Result = await(category, MoveTo.UP)
|
||||
|
||||
suspend fun moveDown(category: Category): Result =
|
||||
await(category, MoveTo.DOWN)
|
||||
suspend fun moveDown(category: Category): Result = await(category, MoveTo.DOWN)
|
||||
|
||||
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
|
||||
mutex.withLock {
|
||||
@@ -57,6 +55,27 @@ class ReorderCategory(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sortAlphabetically() = withNonCancellableContext {
|
||||
mutex.withLock {
|
||||
val updates = categoryRepository.getAll()
|
||||
.sortedBy { category -> category.name }
|
||||
.mapIndexed { index, category ->
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
order = index.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
categoryRepository.updatePartial(updates)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Result {
|
||||
data object Success : Result
|
||||
data object Unchanged : Result
|
||||
|
||||
@@ -28,7 +28,11 @@ class SetSortModeForCategory(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category?, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
||||
suspend fun await(
|
||||
category: Category?,
|
||||
type: LibrarySort.Type,
|
||||
direction: LibrarySort.Direction,
|
||||
) {
|
||||
await(category?.id, type, direction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ object ChapterRecognition {
|
||||
*/
|
||||
private val unwantedWhiteSpace = Regex("""\s(?=extra|special|omake)""")
|
||||
|
||||
fun parseChapterNumber(mangaTitle: String, chapterName: String, chapterNumber: Double? = null): Double {
|
||||
fun parseChapterNumber(
|
||||
mangaTitle: String,
|
||||
chapterName: String,
|
||||
chapterNumber: Double? = null,
|
||||
): Double {
|
||||
// If chapter number is known return.
|
||||
if (chapterNumber != null && (chapterNumber == -2.0 || chapterNumber > -1.0)) {
|
||||
return chapterNumber
|
||||
|
||||
@@ -3,7 +3,13 @@ package tachiyomi.domain.chapter.service
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending()): (Chapter, Chapter) -> Int {
|
||||
fun getChapterSort(
|
||||
manga: Manga,
|
||||
sortDescending: Boolean = manga.sortDescending(),
|
||||
): (
|
||||
Chapter,
|
||||
Chapter,
|
||||
) -> Int {
|
||||
return when (manga.sorting) {
|
||||
Manga.CHAPTER_SORTING_SOURCE -> when (sortDescending) {
|
||||
true -> { c1, c2 -> c1.sourceOrder.compareTo(c2.sourceOrder) }
|
||||
|
||||
@@ -8,9 +8,15 @@ class DownloadPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun downloadsDirectory() = preferenceStore.getString("download_directory", folderProvider.path())
|
||||
fun downloadsDirectory() = preferenceStore.getString(
|
||||
"download_directory",
|
||||
folderProvider.path(),
|
||||
)
|
||||
|
||||
fun downloadOnlyOverWifi() = preferenceStore.getBoolean("pref_download_only_over_wifi_key", true)
|
||||
fun downloadOnlyOverWifi() = preferenceStore.getBoolean(
|
||||
"pref_download_only_over_wifi_key",
|
||||
true,
|
||||
)
|
||||
|
||||
fun saveChaptersAsCBZ() = preferenceStore.getBoolean("save_chapter_as_cbz", true)
|
||||
|
||||
@@ -20,15 +26,27 @@ class DownloadPreferences(
|
||||
|
||||
fun removeAfterReadSlots() = preferenceStore.getInt("remove_after_read_slots", -1)
|
||||
|
||||
fun removeAfterMarkedAsRead() = preferenceStore.getBoolean("pref_remove_after_marked_as_read_key", false)
|
||||
fun removeAfterMarkedAsRead() = preferenceStore.getBoolean(
|
||||
"pref_remove_after_marked_as_read_key",
|
||||
false,
|
||||
)
|
||||
|
||||
fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false)
|
||||
|
||||
fun removeExcludeCategories() = preferenceStore.getStringSet("remove_exclude_categories", emptySet())
|
||||
fun removeExcludeCategories() = preferenceStore.getStringSet(
|
||||
"remove_exclude_categories",
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false)
|
||||
|
||||
fun downloadNewChapterCategories() = preferenceStore.getStringSet("download_new_categories", emptySet())
|
||||
fun downloadNewChapterCategories() = preferenceStore.getStringSet(
|
||||
"download_new_categories",
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
|
||||
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet(
|
||||
"download_new_categories_exclude",
|
||||
emptySet(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ class GetNextChapters(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, fromChapterId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
suspend fun await(
|
||||
mangaId: Long,
|
||||
fromChapterId: Long,
|
||||
onlyUnread: Boolean = true,
|
||||
): List<Chapter> {
|
||||
val chapters = await(mangaId, onlyUnread)
|
||||
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
|
||||
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)
|
||||
|
||||
@@ -65,7 +65,18 @@ data class LibrarySort(
|
||||
}
|
||||
|
||||
companion object {
|
||||
val types by lazy { setOf(Type.Alphabetical, Type.LastRead, Type.LastUpdate, Type.UnreadCount, Type.TotalChapters, Type.LatestChapter, Type.ChapterFetchDate, Type.DateAdded) }
|
||||
val types by lazy {
|
||||
setOf(
|
||||
Type.Alphabetical,
|
||||
Type.LastRead,
|
||||
Type.LastUpdate,
|
||||
Type.UnreadCount,
|
||||
Type.TotalChapters,
|
||||
Type.LatestChapter,
|
||||
Type.ChapterFetchDate,
|
||||
Type.DateAdded,
|
||||
)
|
||||
}
|
||||
val directions by lazy { setOf(Direction.Ascending, Direction.Descending) }
|
||||
val default = LibrarySort(Type.Alphabetical, Direction.Ascending)
|
||||
|
||||
|
||||
@@ -11,9 +11,19 @@ class LibraryPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun displayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
||||
fun displayMode() = preferenceStore.getObject(
|
||||
"pref_display_mode_library",
|
||||
LibraryDisplayMode.default,
|
||||
LibraryDisplayMode.Serializer::serialize,
|
||||
LibraryDisplayMode.Serializer::deserialize,
|
||||
)
|
||||
|
||||
fun sortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
|
||||
fun sortingMode() = preferenceStore.getObject(
|
||||
"library_sorting_mode",
|
||||
LibrarySort.default,
|
||||
LibrarySort.Serializer::serialize,
|
||||
LibrarySort.Serializer::deserialize,
|
||||
)
|
||||
|
||||
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
|
||||
|
||||
@@ -42,31 +52,64 @@ class LibraryPreferences(
|
||||
|
||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
||||
|
||||
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
|
||||
fun showContinueReadingButton() = preferenceStore.getBoolean(
|
||||
"display_continue_reading_button",
|
||||
false,
|
||||
)
|
||||
|
||||
// region Filter
|
||||
|
||||
fun filterDownloaded() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriState.DISABLED)
|
||||
fun filterDownloaded() = preferenceStore.getEnum(
|
||||
"pref_filter_library_downloaded_v2",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriState.DISABLED)
|
||||
|
||||
fun filterStarted() = preferenceStore.getEnum("pref_filter_library_started_v2", TriState.DISABLED)
|
||||
fun filterStarted() = preferenceStore.getEnum(
|
||||
"pref_filter_library_started_v2",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterBookmarked() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriState.DISABLED)
|
||||
fun filterBookmarked() = preferenceStore.getEnum(
|
||||
"pref_filter_library_bookmarked_v2",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriState.DISABLED)
|
||||
fun filterCompleted() = preferenceStore.getEnum(
|
||||
"pref_filter_library_completed_v2",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriState.DISABLED)
|
||||
fun filterIntervalCustom() = preferenceStore.getEnum(
|
||||
"pref_filter_library_interval_custom",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriState.DISABLED)
|
||||
fun filterIntervalLong() = preferenceStore.getEnum(
|
||||
"pref_filter_library_interval_long",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriState.DISABLED)
|
||||
fun filterIntervalLate() = preferenceStore.getEnum(
|
||||
"pref_filter_library_interval_late",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriState.DISABLED)
|
||||
fun filterIntervalDropped() = preferenceStore.getEnum(
|
||||
"pref_filter_library_interval_dropped",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriState.DISABLED)
|
||||
fun filterIntervalPassed() = preferenceStore.getEnum(
|
||||
"pref_filter_library_interval_passed",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriState.DISABLED)
|
||||
fun filterTracking(id: Int) = preferenceStore.getEnum(
|
||||
"pref_filter_library_tracked_${id}_v2",
|
||||
TriState.DISABLED,
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -97,24 +140,45 @@ class LibraryPreferences(
|
||||
|
||||
fun updateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
|
||||
|
||||
fun updateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
|
||||
fun updateCategoriesExclude() = preferenceStore.getStringSet(
|
||||
"library_update_categories_exclude",
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Chapter
|
||||
|
||||
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
|
||||
fun filterChapterByRead() = preferenceStore.getLong(
|
||||
"default_chapter_filter_by_read",
|
||||
Manga.SHOW_ALL,
|
||||
)
|
||||
|
||||
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
|
||||
fun filterChapterByDownloaded() = preferenceStore.getLong(
|
||||
"default_chapter_filter_by_downloaded",
|
||||
Manga.SHOW_ALL,
|
||||
)
|
||||
|
||||
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
|
||||
fun filterChapterByBookmarked() = preferenceStore.getLong(
|
||||
"default_chapter_filter_by_bookmarked",
|
||||
Manga.SHOW_ALL,
|
||||
)
|
||||
|
||||
// and upload date
|
||||
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
|
||||
fun sortChapterBySourceOrNumber() = preferenceStore.getLong(
|
||||
"default_chapter_sort_by_source_or_number",
|
||||
Manga.CHAPTER_SORTING_SOURCE,
|
||||
)
|
||||
|
||||
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
|
||||
fun displayChapterByNameOrNumber() = preferenceStore.getLong(
|
||||
"default_chapter_display_by_name_or_number",
|
||||
Manga.CHAPTER_DISPLAY_NAME,
|
||||
)
|
||||
|
||||
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
|
||||
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong(
|
||||
"default_chapter_sort_by_ascending_or_descending",
|
||||
Manga.CHAPTER_SORT_DESC,
|
||||
)
|
||||
|
||||
fun setChapterSettingsDefault(manga: Manga) {
|
||||
filterChapterByRead().set(manga.unreadFilterRaw)
|
||||
@@ -122,7 +186,9 @@ class LibraryPreferences(
|
||||
filterChapterByBookmarked().set(manga.bookmarkedFilterRaw)
|
||||
sortChapterBySourceOrNumber().set(manga.sorting)
|
||||
displayChapterByNameOrNumber().set(manga.displayMode)
|
||||
sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
|
||||
sortChapterByAscendingOrDescending().set(
|
||||
if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC,
|
||||
)
|
||||
}
|
||||
|
||||
fun autoClearChapterCache() = preferenceStore.getBoolean("auto_clear_chapter_cache", false)
|
||||
@@ -131,9 +197,15 @@ class LibraryPreferences(
|
||||
|
||||
// region Swipe Actions
|
||||
|
||||
fun swipeToStartAction() = preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleBookmark)
|
||||
fun swipeToStartAction() = preferenceStore.getEnum(
|
||||
"pref_chapter_swipe_end_action",
|
||||
ChapterSwipeAction.ToggleBookmark,
|
||||
)
|
||||
|
||||
fun swipeToEndAction() = preferenceStore.getEnum("pref_chapter_swipe_start_action", ChapterSwipeAction.ToggleRead)
|
||||
fun swipeToEndAction() = preferenceStore.getEnum(
|
||||
"pref_chapter_swipe_start_action",
|
||||
ChapterSwipeAction.ToggleRead,
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
@@ -5,14 +5,12 @@ import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
const val MAX_FETCH_INTERVAL = 28
|
||||
private const val FETCH_INTERVAL_GRACE_PERIOD = 1
|
||||
|
||||
class SetFetchInterval(
|
||||
class FetchInterval(
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
) {
|
||||
|
||||
@@ -27,7 +25,10 @@ class SetFetchInterval(
|
||||
window
|
||||
}
|
||||
val chapters = getChapterByMangaId.await(manga.id)
|
||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(chapters, dateTime)
|
||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||
chapters,
|
||||
dateTime.zone,
|
||||
)
|
||||
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
|
||||
|
||||
return if (manga.nextUpdate == nextUpdate && manga.fetchInterval == interval) {
|
||||
@@ -39,31 +40,34 @@ class SetFetchInterval(
|
||||
|
||||
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
|
||||
val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone)
|
||||
val lowerBound = today.minusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
||||
val upperBound = today.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
||||
val lowerBound = today.minusDays(GRACE_PERIOD)
|
||||
val upperBound = today.plusDays(GRACE_PERIOD)
|
||||
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
|
||||
}
|
||||
|
||||
internal fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
|
||||
val sortedChapters = chapters
|
||||
.sortedWith(compareByDescending<Chapter> { it.dateUpload }.thenByDescending { it.dateFetch })
|
||||
.take(50)
|
||||
|
||||
val uploadDates = sortedChapters
|
||||
internal fun calculateInterval(chapters: List<Chapter>, zone: ZoneId): Int {
|
||||
val uploadDates = chapters.asSequence()
|
||||
.filter { it.dateUpload > 0L }
|
||||
.sortedByDescending { it.dateUpload }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
val fetchDates = sortedChapters
|
||||
.take(10)
|
||||
.toList()
|
||||
|
||||
val fetchDates = chapters.asSequence()
|
||||
.sortedByDescending { it.dateFetch }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
.take(10)
|
||||
.toList()
|
||||
|
||||
val interval = when {
|
||||
// Enough upload date from source
|
||||
@@ -82,7 +86,7 @@ class SetFetchInterval(
|
||||
else -> 7
|
||||
}
|
||||
|
||||
return interval.coerceIn(1, MAX_FETCH_INTERVAL)
|
||||
return interval.coerceIn(1, MAX_INTERVAL)
|
||||
}
|
||||
|
||||
private fun calculateNextUpdate(
|
||||
@@ -95,7 +99,10 @@ class SetFetchInterval(
|
||||
manga.nextUpdate !in window.first.rangeTo(window.second + 1) ||
|
||||
manga.fetchInterval == 0
|
||||
) {
|
||||
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), dateTime.zone)
|
||||
val latestDate = ZonedDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(manga.lastUpdate),
|
||||
dateTime.zone,
|
||||
)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
|
||||
@@ -110,7 +117,7 @@ class SetFetchInterval(
|
||||
}
|
||||
|
||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int {
|
||||
if (delta >= MAX_FETCH_INTERVAL) return MAX_FETCH_INTERVAL
|
||||
if (delta >= MAX_INTERVAL) return MAX_INTERVAL
|
||||
|
||||
// double delta again if missed more than 9 check in new delta
|
||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||
@@ -120,4 +127,10 @@ class SetFetchInterval(
|
||||
delta
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_INTERVAL = 28
|
||||
|
||||
private const val GRACE_PERIOD = 1L
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,10 @@ class GetApplicationRelease(
|
||||
val now = Instant.now()
|
||||
|
||||
// Limit checks to once every 3 days at most
|
||||
if (arguments.forceCheck.not() && now.isBefore(Instant.ofEpochMilli(lastChecked.get()).plus(3, ChronoUnit.DAYS))) {
|
||||
if (arguments.forceCheck.not() && now.isBefore(
|
||||
Instant.ofEpochMilli(lastChecked.get()).plus(3, ChronoUnit.DAYS),
|
||||
)
|
||||
) {
|
||||
return Result.NoNewUpdate
|
||||
}
|
||||
|
||||
@@ -29,7 +32,12 @@ class GetApplicationRelease(
|
||||
lastChecked.set(now.toEpochMilli())
|
||||
|
||||
// Check if latest version is different from current version
|
||||
val isNewVersion = isNewVersion(arguments.isPreview, arguments.commitCount, arguments.versionName, release.version)
|
||||
val isNewVersion = isNewVersion(
|
||||
arguments.isPreview,
|
||||
arguments.commitCount,
|
||||
arguments.versionName,
|
||||
release.version,
|
||||
)
|
||||
return when {
|
||||
isNewVersion && arguments.isThirdParty -> Result.ThirdPartyInstallation
|
||||
isNewVersion -> Result.NewUpdate(release)
|
||||
@@ -37,7 +45,12 @@ class GetApplicationRelease(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNewVersion(isPreview: Boolean, commitCount: Int, versionName: String, versionTag: String): Boolean {
|
||||
private fun isNewVersion(
|
||||
isPreview: Boolean,
|
||||
commitCount: Int,
|
||||
versionName: String,
|
||||
versionTag: String,
|
||||
): Boolean {
|
||||
// Removes prefixes like "r" or "v"
|
||||
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
|
||||
return if (isPreview) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import rx.Observable
|
||||
|
||||
class StubSource(
|
||||
override val id: Long,
|
||||
@@ -14,36 +13,16 @@ class StubSource(
|
||||
|
||||
private val isInvalid: Boolean = name.isBlank() || lang.isBlank()
|
||||
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga =
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails"))
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return Observable.error(SourceNotInstalledException())
|
||||
}
|
||||
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> =
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList"))
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.error(SourceNotInstalledException())
|
||||
}
|
||||
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> {
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> =
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList"))
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return Observable.error(SourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||
}
|
||||
override fun toString(): String =
|
||||
if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||
}
|
||||
|
||||
class SourceNotInstalledException : Exception()
|
||||
|
||||
@@ -10,7 +10,7 @@ class GetUpdates(
|
||||
) {
|
||||
|
||||
suspend fun await(read: Boolean, after: Long): List<UpdatesWithRelations> {
|
||||
return repository.awaitWithRead(read, after)
|
||||
return repository.awaitWithRead(read, after, limit = 500)
|
||||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> {
|
||||
@@ -18,6 +18,6 @@ class GetUpdates(
|
||||
}
|
||||
|
||||
fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
|
||||
return repository.subscribeWithRead(read, after)
|
||||
return repository.subscribeWithRead(read, after, limit = 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
|
||||
interface UpdatesRepository {
|
||||
|
||||
suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations>
|
||||
suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List<UpdatesWithRelations>
|
||||
|
||||
fun subscribeAll(after: Long, limit: Long): Flow<List<UpdatesWithRelations>>
|
||||
|
||||
fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>>
|
||||
fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow<List<UpdatesWithRelations>>
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ class LibraryFlagsTest {
|
||||
|
||||
@Test
|
||||
fun `Test Flag plus operator with old flag as base`() {
|
||||
val currentSort = LibrarySort(LibrarySort.Type.UnreadCount, LibrarySort.Direction.Descending)
|
||||
val currentSort = LibrarySort(
|
||||
LibrarySort.Type.UnreadCount,
|
||||
LibrarySort.Direction.Descending,
|
||||
)
|
||||
currentSort.flag shouldBe 0b00001100
|
||||
|
||||
val sort = LibrarySort(LibrarySort.Type.DateAdded, LibrarySort.Direction.Ascending)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package tachiyomi.domain.manga.interactor
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.parallel.Execution
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class FetchIntervalTest {
|
||||
|
||||
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
|
||||
private val testZoneId = ZoneOffset.UTC
|
||||
private var chapter = Chapter.create().copy(
|
||||
dateFetch = testTime.toEpochSecond() * 1000,
|
||||
dateUpload = testTime.toEpochSecond() * 1000,
|
||||
)
|
||||
|
||||
private val fetchInterval = FetchInterval(mockk())
|
||||
|
||||
@Test
|
||||
fun `returns default interval of 7 days when not enough distinct days`() {
|
||||
val chaptersWithUploadDate = (1..50).map {
|
||||
chapterWithTime(chapter, 1.days)
|
||||
}
|
||||
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 7
|
||||
|
||||
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||
it.copy(dateUpload = 0L)
|
||||
}
|
||||
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval based on more recent chapters`() {
|
||||
val oldChapters = (1..5).map {
|
||||
chapterWithTime(chapter, (it * 7).days) // Would have interval of 7 days
|
||||
}
|
||||
val newChapters = (1..10).map {
|
||||
chapterWithTime(chapter, oldChapters.lastUploadDate() + it.days)
|
||||
}
|
||||
|
||||
val chapters = oldChapters + newChapters
|
||||
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 7 days when multiple chapters in 1 day`() {
|
||||
val chapters = (1..10).map {
|
||||
chapterWithTime(chapter, 10.hours)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 7 days when multiple chapters in 2 days`() {
|
||||
val chapters = (1..2).map {
|
||||
chapterWithTime(chapter, 1.days)
|
||||
} + (1..5).map {
|
||||
chapterWithTime(chapter, 2.days)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 1 day when chapters are released every 1 day`() {
|
||||
val chapters = (1..20).map {
|
||||
chapterWithTime(chapter, it.days)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 1 day when delta is less than 1 day`() {
|
||||
val chapters = (1..20).map {
|
||||
chapterWithTime(chapter, (15 * it).hours)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 2 days when chapters are released every 2 days`() {
|
||||
val chapters = (1..20).map {
|
||||
chapterWithTime(chapter, (2 * it).days)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval with floored value when interval is decimal`() {
|
||||
val chaptersWithUploadDate = (1..5).map {
|
||||
chapterWithTime(chapter, (25 * it).hours)
|
||||
}
|
||||
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 1
|
||||
|
||||
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||
it.copy(dateUpload = 0L)
|
||||
}
|
||||
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns interval of 1 day when chapters are released just below every 2 days`() {
|
||||
val chapters = (1..20).map {
|
||||
chapterWithTime(chapter, (43 * it).hours)
|
||||
}
|
||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||
}
|
||||
|
||||
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
||||
val newTime = testTime.plus(duration.toJavaDuration()).toEpochSecond() * 1000
|
||||
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
||||
}
|
||||
|
||||
private fun List<Chapter>.lastUploadDate() =
|
||||
last().dateUpload.toDuration(DurationUnit.MILLISECONDS)
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package tachiyomi.domain.manga.interactor
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.parallel.Execution
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class SetFetchIntervalTest {
|
||||
|
||||
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
|
||||
private var chapter = Chapter.create().copy(
|
||||
dateFetch = testTime.toEpochSecond() * 1000,
|
||||
dateUpload = testTime.toEpochSecond() * 1000,
|
||||
)
|
||||
|
||||
private val setFetchInterval = SetFetchInterval(mockk())
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns default of 7 days when less than 3 distinct days`() {
|
||||
val chapters = (1..2).map {
|
||||
chapterWithTime(chapter, 10.hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns 7 when 5 chapters in 1 day`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, 10.hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns 7 when 7 chapters in 48 hours, 2 day`() {
|
||||
val chapters = (1..2).map {
|
||||
chapterWithTime(chapter, 24.hours)
|
||||
} + (1..5).map {
|
||||
chapterWithTime(chapter, 48.hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns default of 1 day when interval less than 1`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (15 * it).hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
||||
}
|
||||
|
||||
// Normal interval calculation
|
||||
@Test
|
||||
fun `calculateInterval returns 1 when 5 chapters in 120 hours, 5 days`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (24 * it).hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns 2 when 5 chapters in 240 hours, 10 days`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (48 * it).hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns floored value when interval is decimal`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (25 * it).hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns 1 when 5 chapters in 215 hours, 5 days`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (43 * it).hours)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculateInterval returns interval based on fetch time if upload time not available`() {
|
||||
val chapters = (1..5).map {
|
||||
chapterWithTime(chapter, (25 * it).hours).copy(dateUpload = 0L)
|
||||
}
|
||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
||||
}
|
||||
|
||||
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
||||
val newTime = testTime.plus(duration.toJavaDuration()).toEpochSecond() * 1000
|
||||
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,9 @@ class GetApplicationReleaseTest {
|
||||
),
|
||||
)
|
||||
|
||||
(result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(release).release
|
||||
(result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(
|
||||
release,
|
||||
).release
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,7 +108,9 @@ class GetApplicationReleaseTest {
|
||||
),
|
||||
)
|
||||
|
||||
(result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(release).release
|
||||
(result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(
|
||||
release,
|
||||
).release
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user