Reorganize other util files
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package eu.kanade.tachiyomi.util.chapter
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
|
||||
/**
|
||||
* -R> = regex conversion.
|
||||
*/
|
||||
object ChapterRecognition {
|
||||
/**
|
||||
* All cases with Ch.xx
|
||||
* Mokushiroku Alice Vol.1 Ch. 4: Misrepresentation -R> 4
|
||||
*/
|
||||
private val basic = Regex("""(?<=ch\.) *([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""")
|
||||
|
||||
/**
|
||||
* Regex used when only one number occurrence
|
||||
* Example: Bleach 567: Down With Snowwhite -R> 567
|
||||
*/
|
||||
private val occurrence = Regex("""([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""")
|
||||
|
||||
/**
|
||||
* Regex used when manga title removed
|
||||
* Example: Solanin 028 Vol. 2 -> 028 Vol.2 -> 028Vol.2 -R> 028
|
||||
*/
|
||||
private val withoutManga = Regex("""^([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""")
|
||||
|
||||
/**
|
||||
* Regex used to remove unwanted tags
|
||||
* Example Prison School 12 v.1 vol004 version1243 volume64 -R> Prison School 12
|
||||
*/
|
||||
private val unwanted = Regex("""(?<![a-z])(v|ver|vol|version|volume|season|s).?[0-9]+""")
|
||||
|
||||
/**
|
||||
* Regex used to remove unwanted whitespace
|
||||
* Example One Piece 12 special -R> One Piece 12special
|
||||
*/
|
||||
private val unwantedWhiteSpace = Regex("""(\s)(extra|special|omake)""")
|
||||
|
||||
fun parseChapterNumber(chapter: SChapter, manga: SManga) {
|
||||
// If chapter number is known return.
|
||||
if (chapter.chapter_number == -2f || chapter.chapter_number > -1f)
|
||||
return
|
||||
|
||||
// Get chapter title with lower case
|
||||
var name = chapter.name.toLowerCase()
|
||||
|
||||
// Remove comma's from chapter.
|
||||
name = name.replace(',', '.')
|
||||
|
||||
// Remove unwanted white spaces.
|
||||
unwantedWhiteSpace.findAll(name).let {
|
||||
it.forEach { occurrence -> name = name.replace(occurrence.value, occurrence.value.trim()) }
|
||||
}
|
||||
|
||||
// Remove unwanted tags.
|
||||
unwanted.findAll(name).let {
|
||||
it.forEach { occurrence -> name = name.replace(occurrence.value, "") }
|
||||
}
|
||||
|
||||
// Check base case ch.xx
|
||||
if (updateChapter(basic.find(name), chapter))
|
||||
return
|
||||
|
||||
// Check one number occurrence.
|
||||
val occurrences: MutableList<MatchResult> = arrayListOf()
|
||||
occurrence.findAll(name).let {
|
||||
it.forEach { occurrence -> occurrences.add(occurrence) }
|
||||
}
|
||||
|
||||
if (occurrences.size == 1) {
|
||||
if (updateChapter(occurrences[0], chapter))
|
||||
return
|
||||
}
|
||||
|
||||
// Remove manga title from chapter title.
|
||||
val nameWithoutManga = name.replace(manga.title.toLowerCase(), "").trim()
|
||||
|
||||
// Check if first value is number after title remove.
|
||||
if (updateChapter(withoutManga.find(nameWithoutManga), chapter))
|
||||
return
|
||||
|
||||
// Take the first number encountered.
|
||||
if (updateChapter(occurrence.find(nameWithoutManga), chapter))
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if volume is found and update chapter
|
||||
* @param match result of regex
|
||||
* @param chapter chapter object
|
||||
* @return true if volume is found
|
||||
*/
|
||||
private fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean {
|
||||
match?.let {
|
||||
val initial = it.groups[1]?.value?.toFloat()!!
|
||||
val subChapterDecimal = it.groups[2]?.value
|
||||
val subChapterAlpha = it.groups[3]?.value
|
||||
val addition = checkForDecimal(subChapterDecimal, subChapterAlpha)
|
||||
chapter.chapter_number = initial.plus(addition)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for decimal in received strings
|
||||
* @param decimal decimal value of regex
|
||||
* @param alpha alpha value of regex
|
||||
* @return decimal/alpha float value
|
||||
*/
|
||||
private fun checkForDecimal(decimal: String?, alpha: String?): Float {
|
||||
if (!decimal.isNullOrEmpty())
|
||||
return decimal.toFloat()
|
||||
|
||||
if (!alpha.isNullOrEmpty()) {
|
||||
if (alpha.contains("extra"))
|
||||
return .99f
|
||||
|
||||
if (alpha.contains("omake"))
|
||||
return .98f
|
||||
|
||||
if (alpha.contains("special"))
|
||||
return .97f
|
||||
|
||||
if (alpha[0] == '.') {
|
||||
// Take value after (.)
|
||||
return parseAlphaPostFix(alpha[1])
|
||||
} else {
|
||||
return parseAlphaPostFix(alpha[0])
|
||||
}
|
||||
}
|
||||
|
||||
return .0f
|
||||
}
|
||||
|
||||
/**
|
||||
* x.a -> x.1, x.b -> x.2, etc
|
||||
*/
|
||||
private fun parseAlphaPostFix(alpha: Char): Float {
|
||||
return ("0." + (alpha.toInt() - 96).toString()).toFloat()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package eu.kanade.tachiyomi.util.chapter
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
/**
|
||||
* Helper method for syncing the list of chapters from the source with the ones from the database.
|
||||
*
|
||||
* @param db the database.
|
||||
* @param rawSourceChapters a list of chapters from the source.
|
||||
* @param manga the manga of the chapters.
|
||||
* @param source the source of the chapters.
|
||||
* @return a pair of new insertions and deletions.
|
||||
*/
|
||||
fun syncChaptersWithSource(db: DatabaseHelper,
|
||||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
source: Source): Pair<List<Chapter>, List<Chapter>> {
|
||||
|
||||
if (rawSourceChapters.isEmpty()) {
|
||||
throw Exception("No chapters found")
|
||||
}
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
|
||||
val sourceChapters = rawSourceChapters.mapIndexed { i, sChapter ->
|
||||
Chapter.create().apply {
|
||||
copyFrom(sChapter)
|
||||
manga_id = manga.id
|
||||
source_order = i
|
||||
}
|
||||
}
|
||||
|
||||
// Chapters from the source not in db.
|
||||
val toAdd = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters whose metadata have changed.
|
||||
val toChange = mutableListOf<Chapter>()
|
||||
|
||||
for (sourceChapter in sourceChapters) {
|
||||
val dbChapter = dbChapters.find { it.url == sourceChapter.url }
|
||||
|
||||
// Add the chapter if not in db already, or update if the metadata changed.
|
||||
if (dbChapter == null) {
|
||||
toAdd.add(sourceChapter)
|
||||
} else {
|
||||
//this forces metadata update for the main viewable things in the chapter list
|
||||
if (source is HttpSource) {
|
||||
source.prepareNewChapter(sourceChapter, manga)
|
||||
}
|
||||
|
||||
ChapterRecognition.parseChapterNumber(sourceChapter, manga)
|
||||
|
||||
if (shouldUpdateDbChapter(dbChapter, sourceChapter)) {
|
||||
dbChapter.scanlator = sourceChapter.scanlator
|
||||
dbChapter.name = sourceChapter.name
|
||||
dbChapter.date_upload = sourceChapter.date_upload
|
||||
dbChapter.chapter_number = sourceChapter.chapter_number
|
||||
toChange.add(dbChapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recognize number for new chapters.
|
||||
toAdd.forEach {
|
||||
if (source is HttpSource) {
|
||||
source.prepareNewChapter(it, manga)
|
||||
}
|
||||
ChapterRecognition.parseChapterNumber(it, manga)
|
||||
}
|
||||
|
||||
// Chapters from the db not in the source.
|
||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
||||
sourceChapters.any { sourceChapter ->
|
||||
dbChapter.url == sourceChapter.url
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
return Pair(emptyList(), emptyList())
|
||||
}
|
||||
|
||||
val readded = mutableListOf<Chapter>()
|
||||
|
||||
db.inTransaction {
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
if (toDelete.isNotEmpty()) {
|
||||
for (c in toDelete) {
|
||||
if (c.read) {
|
||||
deletedReadChapterNumbers.add(c.chapter_number)
|
||||
}
|
||||
deletedChapterNumbers.add(c.chapter_number)
|
||||
}
|
||||
db.deleteChapters(toDelete).executeAsBlocking()
|
||||
}
|
||||
|
||||
if (toAdd.isNotEmpty()) {
|
||||
// Set the date fetch for new items in reverse order to allow another sorting method.
|
||||
// Sources MUST return the chapters from most to less recent, which is common.
|
||||
var now = Date().time
|
||||
|
||||
for (i in toAdd.indices.reversed()) {
|
||||
val c = toAdd[i]
|
||||
c.date_fetch = now++
|
||||
// Try to mark already read chapters as read when the source deletes them
|
||||
if (c.isRecognizedNumber && c.chapter_number in deletedReadChapterNumbers) {
|
||||
c.read = true
|
||||
}
|
||||
if (c.isRecognizedNumber && c.chapter_number in deletedChapterNumbers) {
|
||||
readded.add(c)
|
||||
}
|
||||
}
|
||||
db.insertChapters(toAdd).executeAsBlocking()
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
db.insertChapters(toChange).executeAsBlocking()
|
||||
}
|
||||
|
||||
// Fix order in source.
|
||||
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
||||
|
||||
// Set manga's last update time to latest chapter's upload time if possible
|
||||
val newestChapter = db.getChapters(manga).executeAsBlocking().maxBy { it.date_upload }
|
||||
manga.last_update = newestChapter?.date_upload ?: manga.last_update
|
||||
db.updateLastUpdated(manga).executeAsBlocking()
|
||||
}
|
||||
|
||||
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
|
||||
}
|
||||
|
||||
//checks if the chapter in db needs updated
|
||||
private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter): Boolean {
|
||||
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
|
||||
dbChapter.date_upload != sourceChapter.date_upload ||
|
||||
dbChapter.chapter_number != sourceChapter.chapter_number
|
||||
}
|
||||
Reference in New Issue
Block a user