extension settings
This commit is contained in:
parent
9c0ef7a788
commit
3368a1bc8d
76 changed files with 2320 additions and 131 deletions
|
@ -0,0 +1,13 @@
|
|||
package tachiyomi.core.metadata.tachiyomi
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class AnimeDetails(
|
||||
val title: String? = null,
|
||||
val author: String? = null,
|
||||
val artist: String? = null,
|
||||
val description: String? = null,
|
||||
val genre: List<String>? = null,
|
||||
val status: Int? = null,
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package tachiyomi.core.metadata.tachiyomi
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class MangaDetails(
|
||||
val title: String? = null,
|
||||
val author: String? = null,
|
||||
val artist: String? = null,
|
||||
val description: String? = null,
|
||||
val genre: List<String>? = null,
|
||||
val status: Int? = null,
|
||||
)
|
22
app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt
Normal file
22
app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt
Normal file
|
@ -0,0 +1,22 @@
|
|||
package tachiyomi.domain.entries
|
||||
|
||||
enum class TriStateFilter {
|
||||
DISABLED, // Disable filter
|
||||
ENABLED_IS, // Enabled with "is" filter
|
||||
ENABLED_NOT, // Enabled with "not" filter
|
||||
;
|
||||
|
||||
fun next(): TriStateFilter {
|
||||
return when (this) {
|
||||
DISABLED -> ENABLED_IS
|
||||
ENABLED_IS -> ENABLED_NOT
|
||||
ENABLED_NOT -> DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun applyFilter(filter: TriStateFilter, predicate: () -> Boolean): Boolean = when (filter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> predicate()
|
||||
TriStateFilter.ENABLED_NOT -> !predicate()
|
||||
}
|
134
app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt
Normal file
134
app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt
Normal file
|
@ -0,0 +1,134 @@
|
|||
package tachiyomi.domain.entries.anime.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import java.io.Serializable
|
||||
import kotlin.math.pow
|
||||
|
||||
data class Anime(
|
||||
val id: Long,
|
||||
val source: Long,
|
||||
val favorite: Boolean,
|
||||
val lastUpdate: Long,
|
||||
val nextUpdate: Long,
|
||||
val calculateInterval: Int,
|
||||
val dateAdded: Long,
|
||||
val viewerFlags: Long,
|
||||
val episodeFlags: Long,
|
||||
val coverLastModified: Long,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val artist: String?,
|
||||
val author: String?,
|
||||
val description: String?,
|
||||
val genre: List<String>?,
|
||||
val status: Long,
|
||||
val thumbnailUrl: String?,
|
||||
val updateStrategy: UpdateStrategy,
|
||||
val initialized: Boolean,
|
||||
) : Serializable {
|
||||
|
||||
val sorting: Long
|
||||
get() = episodeFlags and EPISODE_SORTING_MASK
|
||||
|
||||
val displayMode: Long
|
||||
get() = episodeFlags and EPISODE_DISPLAY_MASK
|
||||
|
||||
val unseenFilterRaw: Long
|
||||
get() = episodeFlags and EPISODE_UNSEEN_MASK
|
||||
|
||||
val downloadedFilterRaw: Long
|
||||
get() = episodeFlags and EPISODE_DOWNLOADED_MASK
|
||||
|
||||
val bookmarkedFilterRaw: Long
|
||||
get() = episodeFlags and EPISODE_BOOKMARKED_MASK
|
||||
|
||||
val skipIntroLength: Int
|
||||
get() = (viewerFlags and ANIME_INTRO_MASK).toInt()
|
||||
|
||||
val nextEpisodeToAir: Int
|
||||
get() = (viewerFlags and ANIME_AIRING_EPISODE_MASK).removeHexZeros(zeros = 2).toInt()
|
||||
|
||||
val nextEpisodeAiringAt: Long
|
||||
get() = (viewerFlags and ANIME_AIRING_TIME_MASK).removeHexZeros(zeros = 6)
|
||||
|
||||
val unseenFilter: TriStateFilter
|
||||
get() = when (unseenFilterRaw) {
|
||||
EPISODE_SHOW_UNSEEN -> TriStateFilter.ENABLED_IS
|
||||
EPISODE_SHOW_SEEN -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
val bookmarkedFilter: TriStateFilter
|
||||
get() = when (bookmarkedFilterRaw) {
|
||||
EPISODE_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
||||
EPISODE_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
return episodeFlags and EPISODE_SORT_DIR_MASK == EPISODE_SORT_DESC
|
||||
}
|
||||
|
||||
private fun Long.removeHexZeros(zeros: Int): Long {
|
||||
val hex = 16.0
|
||||
return this.div(hex.pow(zeros)).toLong()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Generic filter that does not filter anything
|
||||
const val SHOW_ALL = 0x00000000L
|
||||
|
||||
const val EPISODE_SORT_DESC = 0x00000000L
|
||||
const val EPISODE_SORT_ASC = 0x00000001L
|
||||
const val EPISODE_SORT_DIR_MASK = 0x00000001L
|
||||
|
||||
const val EPISODE_SHOW_UNSEEN = 0x00000002L
|
||||
const val EPISODE_SHOW_SEEN = 0x00000004L
|
||||
const val EPISODE_UNSEEN_MASK = 0x00000006L
|
||||
|
||||
const val EPISODE_SHOW_DOWNLOADED = 0x00000008L
|
||||
const val EPISODE_SHOW_NOT_DOWNLOADED = 0x00000010L
|
||||
const val EPISODE_DOWNLOADED_MASK = 0x00000018L
|
||||
|
||||
const val EPISODE_SHOW_BOOKMARKED = 0x00000020L
|
||||
const val EPISODE_SHOW_NOT_BOOKMARKED = 0x00000040L
|
||||
const val EPISODE_BOOKMARKED_MASK = 0x00000060L
|
||||
|
||||
const val EPISODE_SORTING_SOURCE = 0x00000000L
|
||||
const val EPISODE_SORTING_NUMBER = 0x00000100L
|
||||
const val EPISODE_SORTING_UPLOAD_DATE = 0x00000200L
|
||||
const val EPISODE_SORTING_MASK = 0x00000300L
|
||||
|
||||
const val EPISODE_DISPLAY_NAME = 0x00000000L
|
||||
const val EPISODE_DISPLAY_NUMBER = 0x00100000L
|
||||
const val EPISODE_DISPLAY_MASK = 0x00100000L
|
||||
|
||||
const val ANIME_INTRO_MASK = 0x000000000000FFL
|
||||
const val ANIME_AIRING_EPISODE_MASK = 0x00000000FFFF00L
|
||||
const val ANIME_AIRING_TIME_MASK = 0xFFFFFFFF000000L
|
||||
|
||||
fun create() = Anime(
|
||||
id = -1L,
|
||||
url = "",
|
||||
title = "",
|
||||
source = -1L,
|
||||
favorite = false,
|
||||
lastUpdate = 0L,
|
||||
nextUpdate = 0L,
|
||||
calculateInterval = 0,
|
||||
dateAdded = 0L,
|
||||
viewerFlags = 0L,
|
||||
episodeFlags = 0L,
|
||||
coverLastModified = 0L,
|
||||
artist = null,
|
||||
author = null,
|
||||
description = null,
|
||||
genre = null,
|
||||
status = 0L,
|
||||
thumbnailUrl = null,
|
||||
updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||
initialized = false,
|
||||
)
|
||||
}
|
||||
}
|
115
app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt
Normal file
115
app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt
Normal file
|
@ -0,0 +1,115 @@
|
|||
package tachiyomi.domain.entries.manga.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import java.io.Serializable
|
||||
|
||||
data class Manga(
|
||||
val id: Long,
|
||||
val source: Long,
|
||||
val favorite: Boolean,
|
||||
val lastUpdate: Long,
|
||||
val nextUpdate: Long,
|
||||
val calculateInterval: Int,
|
||||
val dateAdded: Long,
|
||||
val viewerFlags: Long,
|
||||
val chapterFlags: Long,
|
||||
val coverLastModified: Long,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val artist: String?,
|
||||
val author: String?,
|
||||
val description: String?,
|
||||
val genre: List<String>?,
|
||||
val status: Long,
|
||||
val thumbnailUrl: String?,
|
||||
val updateStrategy: UpdateStrategy,
|
||||
val initialized: Boolean,
|
||||
) : Serializable {
|
||||
|
||||
val sorting: Long
|
||||
get() = chapterFlags and CHAPTER_SORTING_MASK
|
||||
|
||||
val displayMode: Long
|
||||
get() = chapterFlags and CHAPTER_DISPLAY_MASK
|
||||
|
||||
val unreadFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_UNREAD_MASK
|
||||
|
||||
val downloadedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_DOWNLOADED_MASK
|
||||
|
||||
val bookmarkedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
|
||||
|
||||
val unreadFilter: TriStateFilter
|
||||
get() = when (unreadFilterRaw) {
|
||||
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
val bookmarkedFilter: TriStateFilter
|
||||
get() = when (bookmarkedFilterRaw) {
|
||||
CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
return chapterFlags and CHAPTER_SORT_DIR_MASK == CHAPTER_SORT_DESC
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Generic filter that does not filter anything
|
||||
const val SHOW_ALL = 0x00000000L
|
||||
|
||||
const val CHAPTER_SORT_DESC = 0x00000000L
|
||||
const val CHAPTER_SORT_ASC = 0x00000001L
|
||||
const val CHAPTER_SORT_DIR_MASK = 0x00000001L
|
||||
|
||||
const val CHAPTER_SHOW_UNREAD = 0x00000002L
|
||||
const val CHAPTER_SHOW_READ = 0x00000004L
|
||||
const val CHAPTER_UNREAD_MASK = 0x00000006L
|
||||
|
||||
const val CHAPTER_SHOW_DOWNLOADED = 0x00000008L
|
||||
const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010L
|
||||
const val CHAPTER_DOWNLOADED_MASK = 0x00000018L
|
||||
|
||||
const val CHAPTER_SHOW_BOOKMARKED = 0x00000020L
|
||||
const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040L
|
||||
const val CHAPTER_BOOKMARKED_MASK = 0x00000060L
|
||||
|
||||
const val CHAPTER_SORTING_SOURCE = 0x00000000L
|
||||
const val CHAPTER_SORTING_NUMBER = 0x00000100L
|
||||
const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L
|
||||
const val CHAPTER_SORTING_MASK = 0x00000300L
|
||||
|
||||
const val CHAPTER_DISPLAY_NAME = 0x00000000L
|
||||
const val CHAPTER_DISPLAY_NUMBER = 0x00100000L
|
||||
const val CHAPTER_DISPLAY_MASK = 0x00100000L
|
||||
|
||||
fun create() = Manga(
|
||||
id = -1L,
|
||||
url = "",
|
||||
title = "",
|
||||
source = -1L,
|
||||
favorite = false,
|
||||
lastUpdate = 0L,
|
||||
nextUpdate = 0L,
|
||||
calculateInterval = 0,
|
||||
dateAdded = 0L,
|
||||
viewerFlags = 0L,
|
||||
chapterFlags = 0L,
|
||||
coverLastModified = 0L,
|
||||
artist = null,
|
||||
author = null,
|
||||
description = null,
|
||||
genre = null,
|
||||
status = 0L,
|
||||
thumbnailUrl = null,
|
||||
updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||
initialized = false,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package tachiyomi.domain.items.episode.service
|
||||
|
||||
/**
|
||||
* -R> = regex conversion.
|
||||
*/
|
||||
object EpisodeRecognition {
|
||||
|
||||
private const val NUMBER_PATTERN = """([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?"""
|
||||
|
||||
/**
|
||||
* All cases with Ch.xx
|
||||
* Mokushiroku Alice Vol.1 Ch. 4: Misrepresentation -R> 4
|
||||
*/
|
||||
private val basic = Regex("""(?<=ep\.) *$NUMBER_PATTERN""")
|
||||
|
||||
/**
|
||||
* Example: Bleach 567: Down With Snowwhite -R> 567
|
||||
*/
|
||||
private val number = Regex(NUMBER_PATTERN)
|
||||
|
||||
/**
|
||||
* Regex used to remove unwanted tags
|
||||
* Example Prison School 12 v.1 vol004 version1243 volume64 -R> Prison School 12
|
||||
*/
|
||||
private val unwanted = Regex("""\b(?:v|ver|vol|version|volume|season|s)[^a-z]?[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 parseEpisodeNumber(animeTitle: String, episodeName: String, episodeNumber: Float? = null): Float {
|
||||
// If episode number is known return.
|
||||
if (episodeNumber != null && (episodeNumber == -2f || episodeNumber > -1f)) {
|
||||
return episodeNumber
|
||||
}
|
||||
|
||||
// Get chapter title with lower case
|
||||
var name = episodeName.lowercase()
|
||||
|
||||
// Remove anime title from episode title.
|
||||
name = name.replace(animeTitle.lowercase(), "").trim()
|
||||
|
||||
// Remove comma's or hyphens.
|
||||
name = name.replace(',', '.').replace('-', '.')
|
||||
|
||||
// Remove unwanted white spaces.
|
||||
name = unwantedWhiteSpace.replace(name, "")
|
||||
|
||||
// Remove unwanted tags.
|
||||
name = unwanted.replace(name, "")
|
||||
|
||||
// Check base case ch.xx
|
||||
basic.find(name)?.let { return getEpisodeNumberFromMatch(it) }
|
||||
|
||||
// Take the first number encountered.
|
||||
number.find(name)?.let { return getEpisodeNumberFromMatch(it) }
|
||||
|
||||
return episodeNumber ?: -1f
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if episode number is found and return it
|
||||
* @param match result of regex
|
||||
* @return chapter number if found else null
|
||||
*/
|
||||
private fun getEpisodeNumberFromMatch(match: MatchResult): Float {
|
||||
return 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)
|
||||
initial.plus(addition)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
val trimmedAlpha = alpha.trimStart('.')
|
||||
if (trimmedAlpha.length == 1) {
|
||||
return parseAlphaPostFix(trimmedAlpha[0])
|
||||
}
|
||||
}
|
||||
|
||||
return .0f
|
||||
}
|
||||
|
||||
/**
|
||||
* x.a -> x.1, x.b -> x.2, etc
|
||||
*/
|
||||
private fun parseAlphaPostFix(alpha: Char): Float {
|
||||
val number = alpha.code - ('a'.code - 1)
|
||||
if (number >= 10) return 0f
|
||||
return number / 10f
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package tachiyomi.domain.source.anime.model
|
||||
|
||||
data class AnimeSource(
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val supportsLatest: Boolean,
|
||||
val isStub: Boolean,
|
||||
val pin: Pins = Pins.unpinned,
|
||||
val isUsedLast: Boolean = false,
|
||||
) {
|
||||
|
||||
val visualName: String
|
||||
get() = when {
|
||||
lang.isEmpty() -> name
|
||||
else -> "$name (${lang.uppercase()})"
|
||||
}
|
||||
|
||||
val key: () -> String = {
|
||||
when {
|
||||
isUsedLast -> "$id-lastused"
|
||||
else -> "$id"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package tachiyomi.domain.source.anime.model
|
||||
|
||||
data class AnimeSourceWithCount(
|
||||
val source: AnimeSource,
|
||||
val count: Long,
|
||||
) {
|
||||
|
||||
val id: Long
|
||||
get() = source.id
|
||||
|
||||
val name: String
|
||||
get() = source.name
|
||||
}
|
42
app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt
Normal file
42
app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
package tachiyomi.domain.source.anime.model
|
||||
|
||||
sealed class Pin(val code: Int) {
|
||||
object Unpinned : Pin(0b00)
|
||||
object Pinned : Pin(0b01)
|
||||
object Actual : Pin(0b10)
|
||||
}
|
||||
|
||||
inline fun Pins(builder: Pins.PinsBuilder.() -> Unit = {}): Pins {
|
||||
return Pins.PinsBuilder().apply(builder).flags()
|
||||
}
|
||||
|
||||
fun Pins(vararg pins: Pin) = Pins {
|
||||
pins.forEach { +it }
|
||||
}
|
||||
|
||||
data class Pins(val code: Int = Pin.Unpinned.code) {
|
||||
|
||||
operator fun contains(pin: Pin): Boolean = pin.code and code == pin.code
|
||||
|
||||
operator fun plus(pin: Pin): Pins = Pins(code or pin.code)
|
||||
|
||||
operator fun minus(pin: Pin): Pins = Pins(code xor pin.code)
|
||||
|
||||
companion object {
|
||||
val unpinned = Pins(Pin.Unpinned)
|
||||
|
||||
val pinned = Pins(Pin.Pinned, Pin.Actual)
|
||||
}
|
||||
|
||||
class PinsBuilder(var code: Int = 0) {
|
||||
operator fun Pin.unaryPlus() {
|
||||
this@PinsBuilder.code = code or this@PinsBuilder.code
|
||||
}
|
||||
|
||||
operator fun Pin.unaryMinus() {
|
||||
this@PinsBuilder.code = code or this@PinsBuilder.code
|
||||
}
|
||||
|
||||
fun flags(): Pins = Pins(code)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package tachiyomi.domain.source.anime.model
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource {
|
||||
|
||||
override val id: Long = sourceData.id
|
||||
|
||||
override val name: String = sourceData.name.ifBlank { id.toString() }
|
||||
|
||||
override val lang: String = sourceData.lang
|
||||
|
||||
override suspend fun getAnimeDetails(anime: SAnime): SAnime {
|
||||
throw AnimeSourceNotInstalledException()
|
||||
}
|
||||
|
||||
override suspend fun getEpisodeList(anime: SAnime): List<SEpisode> {
|
||||
throw AnimeSourceNotInstalledException()
|
||||
}
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
throw AnimeSourceNotInstalledException()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||
}
|
||||
}
|
||||
class AnimeSourceNotInstalledException : Exception()
|
|
@ -0,0 +1,27 @@
|
|||
package tachiyomi.domain.source.anime.repository
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.model.AnimeSourceWithCount
|
||||
|
||||
typealias AnimeSourcePagingSourceType = PagingSource<Long, SAnime>
|
||||
|
||||
interface AnimeSourceRepository {
|
||||
|
||||
fun getAnimeSources(): Flow<List<AnimeSource>>
|
||||
|
||||
fun getOnlineAnimeSources(): Flow<List<AnimeSource>>
|
||||
|
||||
fun getAnimeSourcesWithFavoriteCount(): Flow<List<Pair<AnimeSource, Long>>>
|
||||
|
||||
fun getSourcesWithNonLibraryAnime(): Flow<List<AnimeSourceWithCount>>
|
||||
|
||||
fun searchAnime(sourceId: Long, query: String, filterList: AnimeFilterList): AnimeSourcePagingSourceType
|
||||
|
||||
fun getPopularAnime(sourceId: Long): AnimeSourcePagingSourceType
|
||||
|
||||
fun getLatestAnime(sourceId: Long): AnimeSourcePagingSourceType
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package tachiyomi.domain.source.anime.service
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||
|
||||
interface AnimeSourceManager {
|
||||
|
||||
val catalogueSources: Flow<List<AnimeCatalogueSource>>
|
||||
|
||||
fun get(sourceKey: Long): AnimeSource?
|
||||
|
||||
fun getOrStub(sourceKey: Long): AnimeSource
|
||||
|
||||
fun getOnlineSources(): List<AnimeHttpSource>
|
||||
|
||||
fun getCatalogueSources(): List<AnimeCatalogueSource>
|
||||
|
||||
fun getStubSources(): List<StubAnimeSource>
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package tachiyomi.domain.source.manga.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import rx.Observable
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
class StubMangaSource(private val sourceData: MangaSourceData) : MangaSource {
|
||||
|
||||
override val id: Long = sourceData.id
|
||||
|
||||
override val name: String = sourceData.name.ifBlank { id.toString() }
|
||||
|
||||
override val lang: String = sourceData.lang
|
||||
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return Observable.error(SourceNotInstalledException())
|
||||
}
|
||||
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the 1.x 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> {
|
||||
throw SourceNotInstalledException()
|
||||
}
|
||||
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return Observable.error(SourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class SourceNotInstalledException : Exception()
|
|
@ -0,0 +1,22 @@
|
|||
package tachiyomi.domain.source.manga.service
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||
|
||||
interface MangaSourceManager {
|
||||
|
||||
val catalogueSources: Flow<List<CatalogueSource>>
|
||||
|
||||
fun get(sourceKey: Long): MangaSource?
|
||||
|
||||
fun getOrStub(sourceKey: Long): MangaSource
|
||||
|
||||
fun getOnlineSources(): List<HttpSource>
|
||||
|
||||
fun getCatalogueSources(): List<CatalogueSource>
|
||||
|
||||
fun getStubSources(): List<StubMangaSource>
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package tachiyomi.source.local.entries.anime
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.UnmeteredSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
//import eu.kanade.tachiyomi.util.storage.toFFmpegString
|
||||
import kotlinx.serialization.json.Json
|
||||
import rx.Observable
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.source.local.filter.anime.AnimeOrderBy
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LocalAnimeSource(
|
||||
private val context: Context,
|
||||
) : AnimeCatalogueSource, UnmeteredSource {
|
||||
|
||||
private val POPULAR_FILTERS = AnimeFilterList(AnimeOrderBy.Popular(context))
|
||||
private val LATEST_FILTERS = AnimeFilterList(AnimeOrderBy.Latest(context))
|
||||
|
||||
override val name ="Local anime source"
|
||||
|
||||
override val id: Long = ID
|
||||
|
||||
override val lang = "other"
|
||||
|
||||
override fun toString() = name
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
// Browse related
|
||||
override fun fetchPopularAnime(page: Int) = fetchSearchAnime(page, "", POPULAR_FILTERS)
|
||||
|
||||
override fun fetchLatestUpdates(page: Int) = fetchSearchAnime(page, "", LATEST_FILTERS)
|
||||
|
||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||
//return emptyObservable()
|
||||
return Observable.just(AnimesPage(emptyList(), false))
|
||||
}
|
||||
|
||||
// Anime details related
|
||||
override suspend fun getAnimeDetails(anime: SAnime): SAnime = withIOContext {
|
||||
//return empty
|
||||
anime
|
||||
}
|
||||
|
||||
// Episodes
|
||||
override suspend fun getEpisodeList(anime: SAnime): List<SEpisode> {
|
||||
//return empty
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// Filters
|
||||
override fun getFilterList() = AnimeFilterList(AnimeOrderBy.Popular(context))
|
||||
|
||||
// Unused stuff
|
||||
override suspend fun getVideoList(episode: SEpisode) = throw UnsupportedOperationException("Unused")
|
||||
|
||||
companion object {
|
||||
const val ID = 0L
|
||||
const val HELP_URL = "https://aniyomi.org/help/guides/local-anime/"
|
||||
|
||||
private const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||
private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)
|
||||
|
||||
private fun getBaseDirectories(context: Context): Sequence<File> {
|
||||
val localFolder = "Aniyomi" + File.separator + "localanime"
|
||||
return DiskUtil.getExternalStorages(context)
|
||||
.map { File(it.absolutePath, localFolder) }
|
||||
.asSequence()
|
||||
}
|
||||
|
||||
private fun getBaseDirectoriesFiles(context: Context): Sequence<File> {
|
||||
return getBaseDirectories(context)
|
||||
// Get all the files inside all baseDir
|
||||
.flatMap { it.listFiles().orEmpty().toList() }
|
||||
}
|
||||
|
||||
private fun getAnimeDir(animeUrl: String, baseDirsFile: Sequence<File>): File? {
|
||||
return baseDirsFile
|
||||
// Get the first animeDir or null
|
||||
.firstOrNull { it.isDirectory && it.name == animeUrl }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Anime.isLocal(): Boolean = source == LocalAnimeSource.ID
|
||||
|
||||
fun AnimeSource.isLocal(): Boolean = id == LocalAnimeSource.ID
|
|
@ -0,0 +1,70 @@
|
|||
package tachiyomi.source.local.entries.manga
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import rx.Observable
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.source.local.filter.manga.MangaOrderBy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LocalMangaSource(
|
||||
private val context: Context,
|
||||
) : CatalogueSource, UnmeteredSource {
|
||||
|
||||
|
||||
private val POPULAR_FILTERS = FilterList(MangaOrderBy.Popular(context))
|
||||
private val LATEST_FILTERS = FilterList(MangaOrderBy.Latest(context))
|
||||
|
||||
override val name: String = "Local manga source"
|
||||
|
||||
override val id: Long = ID
|
||||
|
||||
override val lang: String = "other"
|
||||
|
||||
override fun toString() = name
|
||||
|
||||
override val supportsLatest: Boolean = true
|
||||
|
||||
// Browse related
|
||||
override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
|
||||
|
||||
override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return Observable.just(MangasPage(emptyList(), false))
|
||||
}
|
||||
|
||||
// Manga details related
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
|
||||
manga
|
||||
}
|
||||
|
||||
// Chapters
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// Filters
|
||||
override fun getFilterList() = FilterList(MangaOrderBy.Popular(context))
|
||||
|
||||
// Unused stuff
|
||||
override suspend fun getPageList(chapter: SChapter) = throw UnsupportedOperationException("Unused")
|
||||
|
||||
companion object {
|
||||
const val ID = 0L
|
||||
const val HELP_URL = "https://aniyomi.org/help/guides/local-manga/"
|
||||
|
||||
private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)
|
||||
}
|
||||
}
|
||||
|
||||
fun Manga.isLocal(): Boolean = source == LocalMangaSource.ID
|
||||
|
||||
fun MangaSource.isLocal(): Boolean = id == LocalMangaSource.ID
|
|
@ -0,0 +1,14 @@
|
|||
package tachiyomi.source.local.filter.anime
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
|
||||
sealed class AnimeOrderBy(context: Context, selection: Selection) : AnimeFilter.Sort(
|
||||
|
||||
"Order by",
|
||||
arrayOf("Title", "Date"),
|
||||
selection,
|
||||
) {
|
||||
class Popular(context: Context) : AnimeOrderBy(context, Selection(0, true))
|
||||
class Latest(context: Context) : AnimeOrderBy(context, Selection(1, false))
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package tachiyomi.source.local.filter.manga
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
sealed class MangaOrderBy(context: Context, selection: Selection) : Filter.Sort(
|
||||
"Order by",
|
||||
arrayOf("Title", "Date"),
|
||||
selection,
|
||||
) {
|
||||
class Popular(context: Context) : MangaOrderBy(context, Selection(0, true))
|
||||
class Latest(context: Context) : MangaOrderBy(context, Selection(1, false))
|
||||
}
|
21
app/src/main/java/tachiyomi/source/local/io/Archive.kt
Normal file
21
app/src/main/java/tachiyomi/source/local/io/Archive.kt
Normal file
|
@ -0,0 +1,21 @@
|
|||
package tachiyomi.source.local.io
|
||||
|
||||
import java.io.File
|
||||
|
||||
object ArchiveAnime {
|
||||
|
||||
private val SUPPORTED_ARCHIVE_TYPES = listOf("mp4", "mkv")
|
||||
|
||||
fun isSupported(file: File): Boolean = with(file) {
|
||||
return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES
|
||||
}
|
||||
}
|
||||
|
||||
object ArchiveManga {
|
||||
|
||||
private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub")
|
||||
|
||||
fun isSupported(file: File): Boolean = with(file) {
|
||||
return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue