extension settings

This commit is contained in:
Finnley Somdahl 2023-10-29 19:45:11 -05:00
parent 9c0ef7a788
commit 3368a1bc8d
76 changed files with 2320 additions and 131 deletions

View file

@ -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,
)

View file

@ -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,
)

View 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()
}

View 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,
)
}
}

View 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,
)
}
}

View file

@ -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
}
}

View file

@ -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"
}
}
}

View file

@ -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
}

View 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)
}
}

View file

@ -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()

View file

@ -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
}

View file

@ -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>
}

View file

@ -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()

View file

@ -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>
}

View file

@ -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

View file

@ -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

View file

@ -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))
}

View file

@ -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))
}

View 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
}
}