fix: allow deprecated media to be played

This commit is contained in:
rebelonion 2024-04-21 02:58:17 -05:00
parent 3c46c21a25
commit 3622d91886
12 changed files with 647 additions and 103 deletions

View file

@ -0,0 +1,381 @@
package ani.dantotsu.download
import android.content.Context
import android.net.Uri
import android.os.Environment
import android.widget.Toast
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity
import ani.dantotsu.currContext
import ani.dantotsu.download.anime.OfflineAnimeModel
import ani.dantotsu.download.manga.OfflineMangaModel
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType
import ani.dantotsu.parsers.Episode
import ani.dantotsu.parsers.MangaChapter
import ani.dantotsu.parsers.MangaImage
import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.SubtitleType
import ani.dantotsu.util.Logger
import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SAnimeImpl
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SChapterImpl
import eu.kanade.tachiyomi.source.model.SManga
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Locale
@Deprecated("external storage is deprecated, use SAF instead")
class DownloadCompat {
companion object {
@Deprecated("external storage is deprecated, use SAF instead")
fun loadMediaCompat(downloadedType: DownloadedType): Media? {
val type = when (downloadedType.type) {
MediaType.MANGA -> "Manga"
MediaType.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.titleName}"
)
//load media.json and convert to media class with gson
return try {
val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
SChapterImpl() // Provide an instance of SChapterImpl
})
.registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> {
SAnimeImpl() // Provide an instance of SAnimeImpl
})
.registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> {
SEpisodeImpl() // Provide an instance of SEpisodeImpl
})
.create()
val media = File(directory, "media.json")
val mediaJson = media.readText()
gson.fromJson(mediaJson, Media::class.java)
} catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
null
}
}
@Deprecated("external storage is deprecated, use SAF instead")
fun loadOfflineAnimeModelCompat(downloadedType: DownloadedType): OfflineAnimeModel {
val type = when (downloadedType.type) {
MediaType.MANGA -> "Manga"
MediaType.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.titleName}"
)
//load media.json and convert to media class with gson
try {
val mediaModel = loadMediaCompat(downloadedType)!!
val cover = File(directory, "cover.jpg")
val coverUri: Uri? = if (cover.exists()) {
Uri.fromFile(cover)
} else null
val banner = File(directory, "banner.jpg")
val bannerUri: Uri? = if (banner.exists()) {
Uri.fromFile(banner)
} else null
val title = mediaModel.mainName()
val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore
?: 0) else mediaModel.userScore) / 10.0).toString()
val isOngoing =
mediaModel.status == currActivity()!!.getString(R.string.status_releasing)
val isUserScored = mediaModel.userScore != 0
val watchedEpisodes = (mediaModel.userProgress ?: "~").toString()
val totalEpisode =
if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString() + " | " + (mediaModel.anime.totalEpisodes
?: "~").toString()) else (mediaModel.anime?.totalEpisodes ?: "~").toString()
val chapters = " Chapters"
val totalEpisodesList =
if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString()) else (mediaModel.anime?.totalEpisodes
?: "~").toString()
return OfflineAnimeModel(
title,
score,
totalEpisode,
totalEpisodesList,
watchedEpisodes,
type,
chapters,
isOngoing,
isUserScored,
coverUri,
bannerUri
)
} catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineAnimeModel(
"unknown",
"0",
"??",
"??",
"??",
"movie",
"hmm",
isOngoing = false,
isUserScored = false,
null,
null
)
}
}
@Deprecated("external storage is deprecated, use SAF instead")
fun loadOfflineMangaModelCompat(downloadedType: DownloadedType): OfflineMangaModel {
val type = when (downloadedType.type) {
MediaType.MANGA -> "Manga"
MediaType.ANIME -> "Anime"
else -> "Novel"
}
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.titleName}"
)
//load media.json and convert to media class with gson
try {
val mediaModel = loadMediaCompat(downloadedType)!!
val cover = File(directory, "cover.jpg")
val coverUri: Uri? = if (cover.exists()) {
Uri.fromFile(cover)
} else null
val banner = File(directory, "banner.jpg")
val bannerUri: Uri? = if (banner.exists()) {
Uri.fromFile(banner)
} else null
val title = mediaModel.mainName()
val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore
?: 0) else mediaModel.userScore) / 10.0).toString()
val isOngoing =
mediaModel.status == currActivity()!!.getString(R.string.status_releasing)
val isUserScored = mediaModel.userScore != 0
val readchapter = (mediaModel.userProgress ?: "~").toString()
val totalchapter = "${mediaModel.manga?.totalChapters ?: "??"}"
val chapters = " Chapters"
return OfflineMangaModel(
title,
score,
totalchapter,
readchapter,
type,
chapters,
isOngoing,
isUserScored,
coverUri,
bannerUri
)
} catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}")
Logger.log(e)
Injekt.get<CrashlyticsInterface>().logException(e)
return OfflineMangaModel(
"unknown",
"0",
"??",
"??",
"movie",
"hmm",
isOngoing = false,
isUserScored = false,
null,
null
)
}
}
@Deprecated("external storage is deprecated, use SAF instead")
suspend fun loadEpisodesCompat(
animeLink: String,
extra: Map<String, String>?,
sAnime: SAnime
): List<Episode> {
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"${animeLocation}/$animeLink"
)
//get all of the folder names and add them to the list
val episodes = mutableListOf<Episode>()
if (directory.exists()) {
directory.listFiles()?.forEach {
//put the title and episdode number in the extra data
val extraData = mutableMapOf<String, String>()
extraData["title"] = animeLink
extraData["episode"] = it.name
if (it.isDirectory) {
val episode = Episode(
it.name,
"$animeLink - ${it.name}",
it.name,
null,
null,
extra = extraData,
sEpisode = SEpisodeImpl()
)
episodes.add(episode)
}
}
episodes.sortBy { MediaNameAdapter.findEpisodeNumber(it.number) }
return episodes
}
return emptyList()
}
@Deprecated("external storage is deprecated, use SAF instead")
suspend fun loadChaptersCompat(
mangaLink: String,
extra: Map<String, String>?,
sManga: SManga
): List<MangaChapter> {
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Manga/$mangaLink"
)
//get all of the folder names and add them to the list
val chapters = mutableListOf<MangaChapter>()
if (directory.exists()) {
directory.listFiles()?.forEach {
if (it.isDirectory) {
val chapter = MangaChapter(
it.name,
"$mangaLink/${it.name}",
it.name,
null,
null,
SChapter.create()
)
chapters.add(chapter)
}
}
chapters.sortBy { MediaNameAdapter.findChapterNumber(it.number) }
return chapters
}
return emptyList()
}
@Deprecated("external storage is deprecated, use SAF instead")
suspend fun loadImagesCompat(chapterLink: String, sChapter: SChapter): List<MangaImage> {
val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Manga/$chapterLink"
)
val images = mutableListOf<MangaImage>()
val imageNumberRegex = Regex("""(\d+)\.jpg$""")
if (directory.exists()) {
directory.listFiles()?.forEach {
if (it.isFile) {
val image = MangaImage(it.absolutePath, false, null)
images.add(image)
}
}
images.sortBy { image ->
val matchResult = imageNumberRegex.find(image.url.url)
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
}
for (image in images) {
Logger.log("imageNumber: ${image.url.url}")
}
return images
}
return emptyList()
}
@Deprecated("external storage is deprecated, use SAF instead")
fun loadSubtitleCompat(title: String, episode: String): List<Subtitle>? {
currContext()?.let {
File(
it.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"$animeLocation/$title/$episode"
).listFiles()?.forEach {
if (it.name.contains("subtitle")) {
return listOf(
Subtitle(
"Downloaded Subtitle",
Uri.fromFile(it).toString(),
determineSubtitletype(it.absolutePath)
)
)
}
}
}
return null
}
private fun determineSubtitletype(url: String): SubtitleType {
return when {
url.lowercase(Locale.ROOT).endsWith("ass") -> SubtitleType.ASS
url.lowercase(Locale.ROOT).endsWith("vtt") -> SubtitleType.VTT
else -> SubtitleType.SRT
}
}
@Deprecated("external storage is deprecated, use SAF instead")
fun removeMediaCompat(context: Context, title: String, type: MediaType) {
val subDirectory = if (type == MediaType.MANGA) {
"Manga"
} else if (type == MediaType.ANIME) {
"Anime"
} else {
"Novel"
}
val directory = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$subDirectory/$title"
)
if (directory.exists()) {
directory.deleteRecursively()
}
}
@Deprecated("external storage is deprecated, use SAF instead")
fun removeDownloadCompat(context: Context, downloadedType: DownloadedType) {
val directory = if (downloadedType.type == MediaType.MANGA) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Manga/${downloadedType.titleName}/${downloadedType.chapterName}"
)
} else if (downloadedType.type == MediaType.ANIME) {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Anime/${downloadedType.titleName}/${downloadedType.chapterName}"
)
} else {
File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Novel/${downloadedType.titleName}/${downloadedType.chapterName}"
)
}
// Check if the directory exists and delete it recursively
if (directory.exists()) {
val deleted = directory.deleteRecursively()
if (deleted) {
Toast.makeText(context, "Successfully deleted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to delete directory", Toast.LENGTH_SHORT).show()
}
}
}
private val animeLocation = "Dantotsu/Anime"
}
}

View file

@ -3,6 +3,8 @@ package ani.dantotsu.download
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import ani.dantotsu.download.DownloadCompat.Companion.removeDownloadCompat
import ani.dantotsu.download.DownloadCompat.Companion.removeMediaCompat
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
@ -58,6 +60,7 @@ class DownloadsManager(private val context: Context) {
toast: Boolean = true, toast: Boolean = true,
onFinished: () -> Unit onFinished: () -> Unit
) { ) {
removeDownloadCompat(context, downloadedType)
downloadsList.remove(downloadedType) downloadsList.remove(downloadedType)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
removeDirectory(downloadedType, toast) removeDirectory(downloadedType, toast)
@ -69,6 +72,7 @@ class DownloadsManager(private val context: Context) {
} }
fun removeMedia(title: String, type: MediaType) { fun removeMedia(title: String, type: MediaType) {
removeMediaCompat(context, title, type)
val baseDirectory = getBaseDirectory(context, type) val baseDirectory = getBaseDirectory(context, type)
val directory = baseDirectory?.findFolder(title) val directory = baseDirectory?.findFolder(title)
if (directory?.exists() == true) { if (directory?.exists() == true) {
@ -84,15 +88,15 @@ class DownloadsManager(private val context: Context) {
} }
when (type) { when (type) {
MediaType.MANGA -> { MediaType.MANGA -> {
downloadsList.removeAll { it.title == title && it.type == MediaType.MANGA } downloadsList.removeAll { it.titleName == title && it.type == MediaType.MANGA }
} }
MediaType.ANIME -> { MediaType.ANIME -> {
downloadsList.removeAll { it.title == title && it.type == MediaType.ANIME } downloadsList.removeAll { it.titleName == title && it.type == MediaType.ANIME }
} }
MediaType.NOVEL -> { MediaType.NOVEL -> {
downloadsList.removeAll { it.title == title && it.type == MediaType.NOVEL } downloadsList.removeAll { it.titleName == title && it.type == MediaType.NOVEL }
} }
} }
saveDownloads() saveDownloads()
@ -115,7 +119,7 @@ class DownloadsManager(private val context: Context) {
if (directory?.exists() == true && directory.isDirectory) { if (directory?.exists() == true && directory.isDirectory) {
val files = directory.listFiles() val files = directory.listFiles()
for (file in files) { for (file in files) {
if (!downloadsSubLists.any { it.title == file.name }) { if (!downloadsSubLists.any { it.titleName == file.name }) {
file.deleteRecursively(context, false) file.deleteRecursively(context, false)
} }
} }
@ -124,8 +128,8 @@ class DownloadsManager(private val context: Context) {
val iterator = downloadsList.iterator() val iterator = downloadsList.iterator()
while (iterator.hasNext()) { while (iterator.hasNext()) {
val download = iterator.next() val download = iterator.next()
val downloadDir = directory?.findFolder(download.title) val downloadDir = directory?.findFolder(download.titleName)
if ((downloadDir?.exists() == false && download.type == type) || download.title.isBlank()) { if ((downloadDir?.exists() == false && download.type == type) || download.titleName.isBlank()) {
iterator.remove() iterator.remove()
} }
} }
@ -211,16 +215,17 @@ class DownloadsManager(private val context: Context) {
fun queryDownload(title: String, chapter: String, type: MediaType? = null): Boolean { fun queryDownload(title: String, chapter: String, type: MediaType? = null): Boolean {
return if (type == null) { return if (type == null) {
downloadsList.any { it.title == title && it.chapter == chapter } downloadsList.any { it.titleName == title && it.chapterName == chapter }
} else { } else {
downloadsList.any { it.title == title && it.chapter == chapter && it.type == type } downloadsList.any { it.titleName == title && it.chapterName == chapter && it.type == type }
} }
} }
private fun removeDirectory(downloadedType: DownloadedType, toast: Boolean) { private fun removeDirectory(downloadedType: DownloadedType, toast: Boolean) {
val baseDirectory = getBaseDirectory(context, downloadedType.type) val baseDirectory = getBaseDirectory(context, downloadedType.type)
val directory = val directory =
baseDirectory?.findFolder(downloadedType.title)?.findFolder(downloadedType.chapter) baseDirectory?.findFolder(downloadedType.titleName)
?.findFolder(downloadedType.chapterName)
downloadsList.remove(downloadedType) downloadsList.remove(downloadedType)
// Check if the directory exists and delete it recursively // Check if the directory exists and delete it recursively
if (directory?.exists() == true) { if (directory?.exists() == true) {
@ -369,10 +374,16 @@ private fun String?.findValidName(): String {
} }
data class DownloadedType( data class DownloadedType(
val pTitle: String, val pChapter: String, val type: MediaType private val pTitle: String?,
private val pChapter: String?,
val type: MediaType,
@Deprecated("use pTitle instead")
private val title: String? = null,
@Deprecated("use pChapter instead")
private val chapter: String? = null
) : Serializable { ) : Serializable {
val title: String val titleName: String
get() = pTitle.findValidName() get() = title?:pTitle.findValidName()
val chapter: String val chapterName: String
get() = pChapter.findValidName() get() = chapter?:pChapter.findValidName()
} }

View file

@ -31,6 +31,8 @@ import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName import ani.dantotsu.download.DownloadsManager.Companion.compareName
@ -175,7 +177,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
// Get the OfflineAnimeModel that was clicked // Get the OfflineAnimeModel that was clicked
val item = adapter.getItem(position) as OfflineAnimeModel val item = adapter.getItem(position) as OfflineAnimeModel
val media = val media =
downloadManager.animeDownloadedTypes.firstOrNull { it.title.compareName(item.title) } downloadManager.animeDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
val mediaModel = getMedia(it) val mediaModel = getMedia(it)
@ -287,10 +289,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} }
downloadsJob = Job() downloadsJob = Job()
CoroutineScope(Dispatchers.IO + downloadsJob).launch { CoroutineScope(Dispatchers.IO + downloadsJob).launch {
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() val animeTitles = downloadManager.animeDownloadedTypes.map { it.titleName }.distinct()
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>() val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
for (title in animeTitles) { for (title in animeTitles) {
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title } val tDownloads = downloadManager.animeDownloadedTypes.filter { it.titleName == title }
val download = tDownloads.first() val download = tDownloads.first()
val offlineAnimeModel = loadOfflineAnimeModel(download) val offlineAnimeModel = loadOfflineAnimeModel(download)
newAnimeDownloads += offlineAnimeModel newAnimeDownloads += offlineAnimeModel
@ -313,7 +315,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
return try { return try {
val directory = DownloadsManager.getSubDirectory( val directory = DownloadsManager.getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
false, downloadedType.title false, downloadedType.titleName
) )
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
@ -327,7 +329,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
}) })
.create() .create()
val media = directory?.findFile("media.json") val media = directory?.findFile("media.json")
?: return null ?: return loadMediaCompat(downloadedType)
val mediaJson = val mediaJson =
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
it?.readText() it?.readText()
@ -352,7 +354,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
try { try {
val directory = DownloadsManager.getSubDirectory( val directory = DownloadsManager.getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
false, downloadedType.title false, downloadedType.titleName
) )
val mediaModel = getMedia(downloadedType)!! val mediaModel = getMedia(downloadedType)!!
val cover = directory?.findFile("cover.jpg") val cover = directory?.findFile("cover.jpg")
@ -391,22 +393,26 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
bannerUri bannerUri
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}") return try {
Logger.log(e) loadOfflineAnimeModelCompat(downloadedType)
Injekt.get<CrashlyticsInterface>().logException(e) } catch (e: Exception) {
return OfflineAnimeModel( Logger.log("Error loading media.json: ${e.message}")
"unknown", Logger.log(e)
"0", Injekt.get<CrashlyticsInterface>().logException(e)
"??", OfflineAnimeModel(
"??", "unknown",
"??", "0",
"movie", "??",
"hmm", "??",
isOngoing = false, "??",
isUserScored = false, "movie",
null, "hmm",
null isOngoing = false,
) isUserScored = false,
null,
null
)
}
} }
} }
} }

View file

@ -28,6 +28,8 @@ import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineMangaModelCompat
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.compareName import ani.dantotsu.download.DownloadsManager.Companion.compareName
@ -169,8 +171,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
// Get the OfflineMangaModel that was clicked // Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel val item = adapter.getItem(position) as OfflineMangaModel
val media = val media =
downloadManager.mangaDownloadedTypes.firstOrNull { it.title.compareName(item.title) } downloadManager.mangaDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title.compareName(item.title) } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.titleName.compareName(item.title) }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
ContextCompat.startActivity( ContextCompat.startActivity(
@ -190,7 +192,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
// Get the OfflineMangaModel that was clicked // Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel val item = adapter.getItem(position) as OfflineMangaModel
val type: MediaType = val type: MediaType =
if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) { if (downloadManager.mangaDownloadedTypes.any { it.titleName == item.title }) {
MediaType.MANGA MediaType.MANGA
} else { } else {
MediaType.NOVEL MediaType.NOVEL
@ -278,19 +280,19 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
downloads = listOf() downloads = listOf()
downloadsJob = Job() downloadsJob = Job()
CoroutineScope(Dispatchers.IO + downloadsJob).launch { CoroutineScope(Dispatchers.IO + downloadsJob).launch {
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.titleName }.distinct()
val newMangaDownloads = mutableListOf<OfflineMangaModel>() val newMangaDownloads = mutableListOf<OfflineMangaModel>()
for (title in mangaTitles) { for (title in mangaTitles) {
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title } val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.titleName == title }
val download = tDownloads.first() val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download) val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel newMangaDownloads += offlineMangaModel
} }
downloads = newMangaDownloads downloads = newMangaDownloads
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val novelTitles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct()
val newNovelDownloads = mutableListOf<OfflineMangaModel>() val newNovelDownloads = mutableListOf<OfflineMangaModel>()
for (title in novelTitles) { for (title in novelTitles) {
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title } val tDownloads = downloadManager.novelDownloadedTypes.filter { it.titleName == title }
val download = tDownloads.first() val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download) val offlineMangaModel = loadOfflineMangaModel(download)
newNovelDownloads += offlineMangaModel newNovelDownloads += offlineMangaModel
@ -315,7 +317,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
return try { return try {
val directory = getSubDirectory( val directory = getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
false, downloadedType.title false, downloadedType.titleName
) )
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
@ -323,7 +325,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}) })
.create() .create()
val media = directory?.findFile("media.json") val media = directory?.findFile("media.json")
?: return null ?: return DownloadCompat.loadMediaCompat(downloadedType)
val mediaJson = val mediaJson =
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use { media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
it?.readText() it?.readText()
@ -343,7 +345,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
try { try {
val directory = getSubDirectory( val directory = getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
false, downloadedType.title false, downloadedType.titleName
) )
val mediaModel = getMedia(downloadedType)!! val mediaModel = getMedia(downloadedType)!!
val cover = directory?.findFile("cover.jpg") val cover = directory?.findFile("cover.jpg")
@ -376,21 +378,25 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
bannerUri bannerUri
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}") return try {
Logger.log(e) loadOfflineMangaModelCompat(downloadedType)
Injekt.get<CrashlyticsInterface>().logException(e) } catch (e: Exception) {
return OfflineMangaModel( Logger.log("Error loading media.json: ${e.message}")
"unknown", Logger.log(e)
"0", Injekt.get<CrashlyticsInterface>().logException(e)
"??", return OfflineMangaModel(
"??", "unknown",
"movie", "0",
"hmm", "??",
isOngoing = false, "??",
isUserScored = false, "movie",
null, "hmm",
null isOngoing = false,
) isUserScored = false,
null,
null
)
}
} }
} }
} }

View file

@ -12,7 +12,17 @@ import androidx.annotation.OptIn
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.HttpDataSource
import androidx.media3.datasource.cache.NoOpCacheEvictor
import androidx.media3.datasource.cache.SimpleCache
import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.offline.Download
import androidx.media3.exoplayer.offline.DownloadManager
import androidx.media3.exoplayer.scheduler.Requirements
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
@ -22,8 +32,12 @@ import ani.dantotsu.media.MediaType
import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.util.concurrent.Executors
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
object Helper { object Helper {
@ -104,4 +118,92 @@ object Helper {
} }
return true return true
} }
@Synchronized
@UnstableApi
@Deprecated("exoplayer download manager is no longer used")
fun downloadManager(context: Context): DownloadManager {
return download ?: let {
val database = Injekt.get<StandaloneDatabaseProvider>()
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
val dataSourceFactory = DataSource.Factory {
//val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
val networkHelper = Injekt.get<NetworkHelper>()
val okHttpClient = networkHelper.client
val dataSource: HttpDataSource =
OkHttpDataSource.Factory(okHttpClient).createDataSource()
defaultHeaders.forEach {
dataSource.setRequestProperty(it.key, it.value)
}
dataSource
}
val threadPoolSize = Runtime.getRuntime().availableProcessors()
val executorService = Executors.newFixedThreadPool(threadPoolSize)
val downloadManager = DownloadManager(
context,
database,
getSimpleCache(context),
dataSourceFactory,
executorService
).apply {
requirements =
Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW)
maxParallelDownloads = 3
}
downloadManager.addListener( //for testing
object : DownloadManager.Listener {
override fun onDownloadChanged(
downloadManager: DownloadManager,
download: Download,
finalException: Exception?
) {
if (download.state == Download.STATE_COMPLETED) {
Logger.log("Download Completed")
} else if (download.state == Download.STATE_FAILED) {
Logger.log("Download Failed")
} else if (download.state == Download.STATE_STOPPED) {
Logger.log("Download Stopped")
} else if (download.state == Download.STATE_QUEUED) {
Logger.log("Download Queued")
} else if (download.state == Download.STATE_DOWNLOADING) {
Logger.log("Download Downloading")
}
}
}
)
downloadManager
}
}
@Deprecated("exoplayer download manager is no longer used")
@OptIn(UnstableApi::class)
fun getSimpleCache(context: Context): SimpleCache {
return if (simpleCache == null) {
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
val database = Injekt.get<StandaloneDatabaseProvider>()
simpleCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), database)
simpleCache!!
} else {
simpleCache!!
}
}
@Synchronized
@Deprecated("exoplayer download manager is no longer used")
private fun getDownloadDirectory(context: Context): File {
if (downloadDirectory == null) {
downloadDirectory = context.getExternalFilesDir(null)
if (downloadDirectory == null) {
downloadDirectory = context.filesDir
}
}
return downloadDirectory!!
}
@Deprecated("exoplayer download manager is no longer used")
private var download: DownloadManager? = null
@Deprecated("exoplayer download manager is no longer used")
private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads"
@Deprecated("exoplayer download manager is no longer used")
private var simpleCache: SimpleCache? = null
@Deprecated("exoplayer download manager is no longer used")
private var downloadDirectory: File? = null
} }

View file

@ -55,8 +55,8 @@ class SubtitleDownloader {
context, context,
downloadedType.type, downloadedType.type,
false, false,
downloadedType.title, downloadedType.titleName,
downloadedType.chapter downloadedType.chapterName
) ?: throw Exception("Could not create directory") ) ?: throw Exception("Could not create directory")
val type = loadSubtitleType(url) val type = loadSubtitleType(url)
directory.findFile("subtitle.${type}")?.delete() directory.findFile("subtitle.${type}")?.delete()

View file

@ -553,8 +553,8 @@ class AnimeWatchFragment : Fragment() {
episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView)) episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView))
episodeAdapter.notifyItemRangeInserted(0, arr.size) episodeAdapter.notifyItemRangeInserted(0, arr.size)
for (download in downloadManager.animeDownloadedTypes) { for (download in downloadManager.animeDownloadedTypes) {
if (media.compareName(download.title)) { if (media.compareName(download.titleName)) {
episodeAdapter.stopDownload(download.chapter) episodeAdapter.stopDownload(download.chapterName)
} }
} }
} }

View file

@ -111,6 +111,7 @@ import ani.dantotsu.connections.updateProgress
import ani.dantotsu.databinding.ActivityExoplayerBinding import ani.dantotsu.databinding.ActivityExoplayerBinding
import ani.dantotsu.defaultHeaders import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.download.video.Helper
import ani.dantotsu.dp import ani.dantotsu.dp
import ani.dantotsu.getCurrentBrightnessValue import ani.dantotsu.getCurrentBrightnessValue
import ani.dantotsu.hideSystemBars import ani.dantotsu.hideSystemBars
@ -1481,26 +1482,38 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
val downloadedMediaItem = if (ext.server.offline) { val downloadedMediaItem = if (ext.server.offline) {
val titleName = ext.server.name.split("/").first() val titleName = ext.server.name.split("/").first()
val episodeName = ext.server.name.split("/").last() val episodeName = ext.server.name.split("/").last()
downloadId = PrefManager.getAnimeDownloadPreferences()
.getString("$titleName - $episodeName", null) ?:
PrefManager.getAnimeDownloadPreferences()
.getString(ext.server.name, null)
val exoItem = if (downloadId != null) {
Helper.downloadManager(this)
.downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem()
} else null
if (exoItem != null) {
exoItem
} else {
val directory = getSubDirectory(this, MediaType.ANIME, false, titleName, episodeName) val directory =
if (directory != null) { getSubDirectory(this, MediaType.ANIME, false, titleName, episodeName)
val files = directory.listFiles() if (directory != null) {
println(files) val files = directory.listFiles()
val docFile = directory.listFiles().firstOrNull { println(files)
it.name?.endsWith(".mp4") == true || it.name?.endsWith(".mkv") == true val docFile = directory.listFiles().firstOrNull {
} it.name?.endsWith(".mp4") == true || it.name?.endsWith(".mkv") == true
if (docFile != null) { }
val uri = docFile.uri if (docFile != null) {
MediaItem.Builder().setUri(uri).setMimeType(mimeType).build() val uri = docFile.uri
MediaItem.Builder().setUri(uri).setMimeType(mimeType).build()
} else {
snackString("File not found")
null
}
} else { } else {
snackString("File not found") snackString("Directory not found")
null null
} }
} else {
snackString("Directory not found")
null
} }
} else null } else null
mediaItem = if (downloadedMediaItem == null) { mediaItem = if (downloadedMediaItem == null) {

View file

@ -194,8 +194,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
) )
for (download in downloadManager.mangaDownloadedTypes) { for (download in downloadManager.mangaDownloadedTypes) {
if (media.compareName(download.title)) { if (media.compareName(download.titleName)) {
chapterAdapter.stopDownload(download.chapter) chapterAdapter.stopDownload(download.chapterName)
} }
} }

View file

@ -2,6 +2,8 @@ package ani.dantotsu.parsers
import android.app.Application import android.app.Application
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadCompat.Companion.loadEpisodesCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadSubtitleCompat
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Companion.getTaskName
@ -53,8 +55,12 @@ class OfflineAnimeParser : AnimeParser() {
episodes.add(episode) episodes.add(episode)
} }
} }
episodes.sortBy { MediaNameAdapter.findEpisodeNumber(it.number) } return if (episodes.isNotEmpty()) {
return episodes episodes.sortBy { MediaNameAdapter.findEpisodeNumber(it.number) }
episodes
} else {
loadEpisodesCompat(animeLink, extra, sAnime)
}
} }
return emptyList() return emptyList()
} }
@ -75,14 +81,16 @@ class OfflineAnimeParser : AnimeParser() {
override suspend fun search(query: String): List<ShowResponse> { override suspend fun search(query: String): List<ShowResponse> {
val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() val titles = downloadManager.animeDownloadedTypes.map { it.titleName }.distinct()
val returnTitles: MutableList<String> = mutableListOf() val returnTitlesPair: MutableList<Pair<String, Int>> = mutableListOf()
for (title in titles) { for (title in titles) {
Logger.log("Comparing $title to $query") Logger.log("Comparing $title to $query")
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { val score = FuzzySearch.ratio(title.lowercase(), query.lowercase())
returnTitles.add(title) if (score > 80) {
returnTitlesPair.add(Pair(title, score))
} }
} }
val returnTitles = returnTitlesPair.sortedByDescending { it.second }.map { it.first }
val returnList: MutableList<ShowResponse> = mutableListOf() val returnList: MutableList<ShowResponse> = mutableListOf()
for (title in returnTitles) { for (title in returnTitles) {
returnList.add(ShowResponse(title, title, title)) returnList.add(ShowResponse(title, title, title))
@ -148,6 +156,7 @@ class OfflineVideoExtractor(private val videoServer: VideoServer) : VideoExtract
) )
} }
} }
loadSubtitleCompat(title, episode)?.let { return it }
} }
return null return null
} }

View file

@ -1,6 +1,8 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.app.Application import android.app.Application
import ani.dantotsu.download.DownloadCompat.Companion.loadChaptersCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadImagesCompat
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
@ -41,8 +43,12 @@ class OfflineMangaParser : MangaParser() {
chapters.add(chapter) chapters.add(chapter)
} }
} }
chapters.sortBy { MediaNameAdapter.findChapterNumber(it.number) } return if (chapters.isNotEmpty()) {
return chapters chapters.sortBy { MediaNameAdapter.findChapterNumber(it.number) }
chapters
} else {
loadChaptersCompat(mangaLink, extra, sManga)
}
} }
return emptyList() return emptyList()
} }
@ -60,26 +66,32 @@ class OfflineMangaParser : MangaParser() {
images.add(image) images.add(image)
} }
} }
images.sortBy { image ->
val matchResult = imageNumberRegex.find(image.url.url)
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
}
for (image in images) { for (image in images) {
Logger.log("imageNumber: ${image.url.url}") Logger.log("imageNumber: ${image.url.url}")
} }
return images return if (images.isNotEmpty()) {
images.sortBy { image ->
val matchResult = imageNumberRegex.find(image.url.url)
matchResult?.groups?.get(1)?.value?.toIntOrNull() ?: Int.MAX_VALUE
}
images
} else {
loadImagesCompat(chapterLink, sChapter)
}
} }
return emptyList() return emptyList()
} }
override suspend fun search(query: String): List<ShowResponse> { override suspend fun search(query: String): List<ShowResponse> {
val titles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val titles = downloadManager.mangaDownloadedTypes.map { it.titleName }.distinct()
val returnTitles: MutableList<String> = mutableListOf() val returnTitlesPair: MutableList<Pair<String, Int>> = mutableListOf()
for (title in titles) { for (title in titles) {
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { val score = FuzzySearch.ratio(title.lowercase(), query.lowercase())
returnTitles.add(title) if (score > 80) {
returnTitlesPair.add(Pair(title, score))
} }
} }
val returnTitles = returnTitlesPair.sortedByDescending { it.second }.map { it.first }
val returnList: MutableList<ShowResponse> = mutableListOf() val returnList: MutableList<ShowResponse> = mutableListOf()
for (title in returnTitles) { for (title in returnTitles) {
returnList.add(ShowResponse(title, title, title)) returnList.add(ShowResponse(title, title, title))

View file

@ -5,6 +5,7 @@ import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.util.Logger
import me.xdrop.fuzzywuzzy.FuzzySearch import me.xdrop.fuzzywuzzy.FuzzySearch
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -48,13 +49,16 @@ class OfflineNovelParser : NovelParser() {
} }
override suspend fun search(query: String): List<ShowResponse> { override suspend fun search(query: String): List<ShowResponse> {
val titles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val titles = downloadManager.novelDownloadedTypes.map { it.titleName }.distinct()
val returnTitles: MutableList<String> = mutableListOf() val returnTitlesPair: MutableList<Pair<String, Int>> = mutableListOf()
for (title in titles) { for (title in titles) {
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { Logger.log("Comparing $title to $query")
returnTitles.add(title) val score = FuzzySearch.ratio(title.lowercase(), query.lowercase())
if (score > 80) {
returnTitlesPair.add(Pair(title, score))
} }
} }
val returnTitles = returnTitlesPair.sortedByDescending { it.second }.map { it.first }
val returnList: MutableList<ShowResponse> = mutableListOf() val returnList: MutableList<ShowResponse> = mutableListOf()
for (title in returnTitles) { for (title in returnTitles) {
//need to search the subdirectories for the ShowResponses //need to search the subdirectories for the ShowResponses