mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-19 22:39:18 +01:00
Imported implementation for updating library by next expected update from Neko (#5436)
* Imported implementation for updating library by next expected update from Neko. This sort uses the last 4 updates for a manga to compute an average time between updates and then extrapolates when the next update should occur. Currently seems to work perfectly. However, I may have silently messed something up along the way. All code and algorithms are credited to kyjibo on GitHub. The original commit adding this functionality is here:681003926a
* Imported implementation for updating library by next expected update from Neko. This sort uses the last 4 updates for a manga to compute an average time between updates and then extrapolates when the next update should occur. Currently seems to work perfectly. However, I may have silently messed something up along the way. All code and algorithms are credited to kyjibo on GitHub. The original commit adding this functionality is here:681003926a
* Remove commented-out line from LibraryUpdateRanker I missed removing this when first committing. The removed line is a holdover from Neko, which requires 7+, but I removed the function that requires this.
This commit is contained in:
parent
3c67a36b60
commit
70ed49e478
@ -20,7 +20,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
/**
|
/**
|
||||||
* Version of the database.
|
* Version of the database.
|
||||||
*/
|
*/
|
||||||
const val DATABASE_VERSION = 11
|
const val DATABASE_VERSION = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||||
@ -82,6 +82,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
db.execSQL(MangaTable.addDateAdded)
|
db.execSQL(MangaTable.addDateAdded)
|
||||||
db.execSQL(MangaTable.backfillDateAdded)
|
db.execSQL(MangaTable.backfillDateAdded)
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 12) {
|
||||||
|
db.execSQL(MangaTable.addNextUpdateCol)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||||
|
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_NEXT_UPDATE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
|
||||||
@ -62,6 +63,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
COL_THUMBNAIL_URL to obj.thumbnail_url,
|
COL_THUMBNAIL_URL to obj.thumbnail_url,
|
||||||
COL_FAVORITE to obj.favorite,
|
COL_FAVORITE to obj.favorite,
|
||||||
COL_LAST_UPDATE to obj.last_update,
|
COL_LAST_UPDATE to obj.last_update,
|
||||||
|
COL_NEXT_UPDATE to obj.next_update,
|
||||||
COL_INITIALIZED to obj.initialized,
|
COL_INITIALIZED to obj.initialized,
|
||||||
COL_VIEWER to obj.viewer_flags,
|
COL_VIEWER to obj.viewer_flags,
|
||||||
COL_CHAPTER_FLAGS to obj.chapter_flags,
|
COL_CHAPTER_FLAGS to obj.chapter_flags,
|
||||||
@ -84,6 +86,7 @@ interface BaseMangaGetResolver {
|
|||||||
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
|
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
|
||||||
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
|
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
|
||||||
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
|
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
|
||||||
|
next_update = cursor.getLong(cursor.getColumnIndex(COL_NEXT_UPDATE))
|
||||||
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
||||||
viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||||
|
@ -15,6 +15,8 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
var last_update: Long
|
var last_update: Long
|
||||||
|
|
||||||
|
var next_update: Long
|
||||||
|
|
||||||
var date_added: Long
|
var date_added: Long
|
||||||
|
|
||||||
var viewer_flags: Int
|
var viewer_flags: Int
|
||||||
|
@ -26,6 +26,8 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var last_update: Long = 0
|
override var last_update: Long = 0
|
||||||
|
|
||||||
|
override var next_update: Long = 0
|
||||||
|
|
||||||
override var date_added: Long = 0
|
override var date_added: Long = 0
|
||||||
|
|
||||||
override var initialized: Boolean = false
|
override var initialized: Boolean = false
|
||||||
|
@ -6,12 +6,7 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
|||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.*
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
@ -97,6 +92,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags, true))
|
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags, true))
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateNextUpdated(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaNextUpdatedPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun updateLastUpdated(manga: Manga) = db.put()
|
fun updateLastUpdated(manga: Manga) = db.put()
|
||||||
.`object`(manga)
|
.`object`(manga)
|
||||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaNextUpdatedPutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_NEXT_UPDATE, manga.next_update)
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,8 @@ object MangaTable {
|
|||||||
|
|
||||||
const val COL_LAST_UPDATE = "last_update"
|
const val COL_LAST_UPDATE = "last_update"
|
||||||
|
|
||||||
|
const val COL_NEXT_UPDATE = "next_update"
|
||||||
|
|
||||||
const val COL_DATE_ADDED = "date_added"
|
const val COL_DATE_ADDED = "date_added"
|
||||||
|
|
||||||
const val COL_INITIALIZED = "initialized"
|
const val COL_INITIALIZED = "initialized"
|
||||||
@ -57,6 +59,7 @@ object MangaTable {
|
|||||||
$COL_THUMBNAIL_URL TEXT,
|
$COL_THUMBNAIL_URL TEXT,
|
||||||
$COL_FAVORITE INTEGER NOT NULL,
|
$COL_FAVORITE INTEGER NOT NULL,
|
||||||
$COL_LAST_UPDATE LONG,
|
$COL_LAST_UPDATE LONG,
|
||||||
|
$COL_NEXT_UPDATE LONG,
|
||||||
$COL_INITIALIZED BOOLEAN NOT NULL,
|
$COL_INITIALIZED BOOLEAN NOT NULL,
|
||||||
$COL_VIEWER INTEGER NOT NULL,
|
$COL_VIEWER INTEGER NOT NULL,
|
||||||
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
||||||
@ -86,4 +89,7 @@ object MangaTable {
|
|||||||
"FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " +
|
"FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " +
|
||||||
"ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " +
|
"ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " +
|
||||||
"GROUP BY $TABLE.$COL_ID)"
|
"GROUP BY $TABLE.$COL_ID)"
|
||||||
|
|
||||||
|
val addNextUpdateCol: String
|
||||||
|
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_NEXT_UPDATE LONG DEFAULT 0"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.data.library
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import java.util.Collections
|
||||||
|
import kotlin.Comparator
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will provide various functions to rank manga to efficiently schedule manga to update.
|
* This class will provide various functions to rank manga to efficiently schedule manga to update.
|
||||||
@ -9,9 +12,26 @@ object LibraryUpdateRanker {
|
|||||||
|
|
||||||
val rankingScheme = listOf(
|
val rankingScheme = listOf(
|
||||||
(this::lexicographicRanking)(),
|
(this::lexicographicRanking)(),
|
||||||
(this::latestFirstRanking)()
|
(this::latestFirstRanking)(),
|
||||||
|
(this::nextFirstRanking)()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a total ordering over all the Mangas.
|
||||||
|
*
|
||||||
|
* Orders the manga based on the distance between the next expected update and now.
|
||||||
|
* The comparator is reversed, placing the smallest (and thus closest to updating now) first.
|
||||||
|
*/
|
||||||
|
fun nextFirstRanking(): Comparator<Manga> {
|
||||||
|
val time = System.currentTimeMillis()
|
||||||
|
return Collections.reverseOrder(
|
||||||
|
Comparator { mangaFirst: Manga,
|
||||||
|
mangaSecond: Manga ->
|
||||||
|
compareValues(abs(mangaSecond.next_update - time), abs(mangaFirst.next_update - time))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a total ordering over all the [Manga]s.
|
* Provides a total ordering over all the [Manga]s.
|
||||||
*
|
*
|
||||||
|
@ -251,7 +251,8 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
// ../../data/library/LibraryUpdateRanker.kt
|
// ../../data/library/LibraryUpdateRanker.kt
|
||||||
val priorities = arrayOf(
|
val priorities = arrayOf(
|
||||||
Pair("0", R.string.action_sort_alpha),
|
Pair("0", R.string.action_sort_alpha),
|
||||||
Pair("1", R.string.action_sort_last_checked)
|
Pair("1", R.string.action_sort_last_checked),
|
||||||
|
Pair("2", R.string.action_sort_next_updated)
|
||||||
)
|
)
|
||||||
val defaultPriority = priorities[0]
|
val defaultPriority = priorities[0]
|
||||||
|
|
||||||
|
@ -96,6 +96,24 @@ fun syncChaptersWithSource(
|
|||||||
|
|
||||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||||
|
val topChapters = dbChapters.sortedByDescending { it.date_upload }.take(4)
|
||||||
|
val newestDate = topChapters.getOrNull(0)?.date_upload ?: 0L
|
||||||
|
|
||||||
|
// Recalculate update rate if unset and enough chapters are present
|
||||||
|
if (manga.next_update == 0L && topChapters.size > 1) {
|
||||||
|
var delta = 0L
|
||||||
|
for (i in 0 until topChapters.size - 1) {
|
||||||
|
delta += (topChapters[i].date_upload - topChapters[i + 1].date_upload)
|
||||||
|
}
|
||||||
|
delta /= topChapters.size - 1
|
||||||
|
manga.next_update = newestDate + delta
|
||||||
|
db.updateNextUpdated(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newestDate != 0L && newestDate != manga.last_update) {
|
||||||
|
manga.last_update = newestDate
|
||||||
|
db.updateLastUpdated(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
return Pair(emptyList(), emptyList())
|
return Pair(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,11 +158,29 @@ fun syncChaptersWithSource(
|
|||||||
db.insertChapters(toChange).executeAsBlocking()
|
db.insertChapters(toChange).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val topChapters = db.getChapters(manga).executeAsBlocking().sortedByDescending { it.date_upload }.take(4)
|
||||||
|
// Recalculate next update since chapters were changed
|
||||||
|
if (topChapters.size > 1) {
|
||||||
|
var delta = 0L
|
||||||
|
for (i in 0 until topChapters.size - 1) {
|
||||||
|
delta += (topChapters[i].date_upload - topChapters[i + 1].date_upload)
|
||||||
|
}
|
||||||
|
delta /= topChapters.size - 1
|
||||||
|
manga.next_update = topChapters[0].date_upload + delta
|
||||||
|
db.updateNextUpdated(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
|
||||||
// Fix order in source.
|
// Fix order in source.
|
||||||
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
// Set this manga as updated since chapters were changed
|
||||||
|
val newestChapter = topChapters.getOrNull(0)
|
||||||
|
val dateFetch = newestChapter?.date_upload ?: manga.last_update
|
||||||
|
if (dateFetch == 0L) {
|
||||||
|
if (toAdd.isNotEmpty()) {
|
||||||
manga.last_update = Date().time
|
manga.last_update = Date().time
|
||||||
|
}
|
||||||
|
} else manga.last_update = dateFetch
|
||||||
db.updateLastUpdated(manga).executeAsBlocking()
|
db.updateLastUpdated(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
<string name="action_sort_total">Total chapters</string>
|
<string name="action_sort_total">Total chapters</string>
|
||||||
<string name="action_sort_last_read">Last read</string>
|
<string name="action_sort_last_read">Last read</string>
|
||||||
<string name="action_sort_last_checked">Last checked</string>
|
<string name="action_sort_last_checked">Last checked</string>
|
||||||
|
<string name="action_sort_next_updated">Next expected update</string>
|
||||||
<string name="action_sort_latest_chapter">Latest chapter</string>
|
<string name="action_sort_latest_chapter">Latest chapter</string>
|
||||||
<string name="action_sort_chapter_fetch_date">Date fetched</string>
|
<string name="action_sort_chapter_fetch_date">Date fetched</string>
|
||||||
<string name="action_sort_date_added">Date added</string>
|
<string name="action_sort_date_added">Date added</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user