package suwayomi.anime.impl /* * Copyright (C) Contributors to the Suwayomi project * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import eu.kanade.tachiyomi.source.model.SAnime import eu.kanade.tachiyomi.source.model.SEpisode import org.jetbrains.exposed.sql.SortOrder.DESC import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update import suwayomi.anime.impl.Anime.getAnime import suwayomi.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource import suwayomi.anime.model.dataclass.EpisodeDataClass import suwayomi.anime.model.table.AnimeTable import suwayomi.anime.model.table.EpisodeTable import suwayomi.anime.model.table.toDataClass import suwayomi.tachidesk.impl.util.lang.awaitSingle object Episode { /** get episode list when showing an anime */ suspend fun getEpisodeList(animeId: Int, onlineFetch: Boolean?): List { return if (onlineFetch == true) { getSourceEpisodes(animeId) } else { transaction { EpisodeTable.select { EpisodeTable.anime eq animeId }.orderBy(EpisodeTable.episodeIndex to DESC) .map { EpisodeTable.toDataClass(it) } }.ifEmpty { // If it was explicitly set to offline dont grab episodes if (onlineFetch == null) { getSourceEpisodes(animeId) } else emptyList() } } } private suspend fun getSourceEpisodes(animeId: Int): List { val animeDetails = getAnime(animeId) val source = getAnimeHttpSource(animeDetails.sourceId.toLong()) val episodeList = source.fetchEpisodeList( SAnime.create().apply { title = animeDetails.title url = animeDetails.url } ).awaitSingle() val episodeCount = episodeList.count() transaction { episodeList.reversed().forEachIndexed { index, fetchedEpisode -> val episodeEntry = EpisodeTable.select { EpisodeTable.url eq fetchedEpisode.url }.firstOrNull() if (episodeEntry == null) { EpisodeTable.insert { it[url] = fetchedEpisode.url it[name] = fetchedEpisode.name it[date_upload] = fetchedEpisode.date_upload it[episode_number] = fetchedEpisode.episode_number it[scanlator] = fetchedEpisode.scanlator it[episodeIndex] = index + 1 it[anime] = animeId } } else { EpisodeTable.update({ EpisodeTable.url eq fetchedEpisode.url }) { it[name] = fetchedEpisode.name it[date_upload] = fetchedEpisode.date_upload it[episode_number] = fetchedEpisode.episode_number it[scanlator] = fetchedEpisode.scanlator it[episodeIndex] = index + 1 it[anime] = animeId } } } } // clear any orphaned episodes that are in the db but not in `episodeList` val dbEpisodeCount = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId }.count() } if (dbEpisodeCount > episodeCount) { // we got some clean up due val dbEpisodeList = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId } } dbEpisodeList.forEach { if (it[EpisodeTable.episodeIndex] >= episodeList.size || episodeList[it[EpisodeTable.episodeIndex] - 1].url != it[EpisodeTable.url] ) { transaction { // PageTable.deleteWhere { PageTable.episode eq it[EpisodeTable.id] } EpisodeTable.deleteWhere { EpisodeTable.id eq it[EpisodeTable.id] } } } } } val dbEpisodeMap = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId } .associateBy({ it[EpisodeTable.url] }, { it }) } return episodeList.mapIndexed { index, it -> val dbEpisode = dbEpisodeMap.getValue(it.url) EpisodeDataClass( it.url, it.name, it.date_upload, it.episode_number, it.scanlator, animeId, dbEpisode[EpisodeTable.isRead], dbEpisode[EpisodeTable.isBookmarked], dbEpisode[EpisodeTable.lastPageRead], episodeCount - index, episodeList.size ) } } /** used to display a episode, get a episode in order to show it's video */ suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass { val episode = getEpisodeList(animeId, false) .first { it.index == episodeIndex } val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() } val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference]) val fetchedLinkUrl = source.fetchEpisodeLink( SEpisode.create().also { it.url = episode.url it.name = episode.name } ).awaitSingle() return EpisodeDataClass( episode.url, episode.name, episode.uploadDate, episode.episodeNumber, episode.scanlator, animeId, episode.read, episode.bookmarked, episode.lastPageRead, episode.index, episode.episodeCount, fetchedLinkUrl ) } // /** used to display a episode, get a episode in order to show it's pages */ // suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass { // val episodeEntry = transaction { // EpisodeTable.select { // (EpisodeTable.episodeIndex eq episodeIndex) and (EpisodeTable.anime eq animeId) // }.first() // } // val animeEntry = transaction { MangaTable.select { MangaTable.id eq animeId }.first() } // val source = getAnimeHttpSource(animeEntry[MangaTable.sourceReference]) // // val pageList = source.fetchPageList( // SEpisode.create().apply { // url = episodeEntry[EpisodeTable.url] // name = episodeEntry[EpisodeTable.name] // } // ).awaitSingle() // // val episodeId = episodeEntry[EpisodeTable.id].value // val episodeCount = transaction { EpisodeTable.select { EpisodeTable.anime eq animeId }.count() } // // // update page list for this episode // transaction { // pageList.forEach { page -> // val pageEntry = transaction { PageTable.select { (PageTable.episode eq episodeId) and (PageTable.index eq page.index) }.firstOrNull() } // if (pageEntry == null) { // PageTable.insert { // it[index] = page.index // it[url] = page.url // it[imageUrl] = page.imageUrl // it[episode] = episodeId // } // } else { // PageTable.update({ (PageTable.episode eq episodeId) and (PageTable.index eq page.index) }) { // it[url] = page.url // it[imageUrl] = page.imageUrl // } // } // } // } // // return EpisodeDataClass( // episodeEntry[EpisodeTable.url], // episodeEntry[EpisodeTable.name], // episodeEntry[EpisodeTable.date_upload], // episodeEntry[EpisodeTable.episode_number], // episodeEntry[EpisodeTable.scanlator], // animeId, // episodeEntry[EpisodeTable.isRead], // episodeEntry[EpisodeTable.isBookmarked], // episodeEntry[EpisodeTable.lastPageRead], // // episodeEntry[EpisodeTable.episodeIndex], // episodeCount.toInt(), // pageList.count() // ) // } fun modifyEpisode(animeId: Int, episodeIndex: Int, isRead: Boolean?, isBookmarked: Boolean?, markPrevRead: Boolean?, lastPageRead: Int?) { transaction { if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) { EpisodeTable.update({ (EpisodeTable.anime eq animeId) and (EpisodeTable.episodeIndex eq episodeIndex) }) { update -> isRead?.also { update[EpisodeTable.isRead] = it } isBookmarked?.also { update[EpisodeTable.isBookmarked] = it } lastPageRead?.also { update[EpisodeTable.lastPageRead] = it } } } markPrevRead?.let { EpisodeTable.update({ (EpisodeTable.anime eq animeId) and (EpisodeTable.episodeIndex less episodeIndex) }) { it[EpisodeTable.isRead] = markPrevRead } } } } }