From d16fbd9a43589e9b5c1199a9e89807e363f90c94 Mon Sep 17 00:00:00 2001 From: Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> Date: Sat, 30 Dec 2023 05:12:46 -0600 Subject: [PATCH] first working version of anime downloads --- app/src/main/AndroidManifest.xml | 2 +- .../ani/dantotsu/download/DownloadsManager.kt | 104 +++++++++-------- .../download/anime/AnimeDownloaderService.kt | 55 ++++++--- .../download/manga/MangaDownloaderService.kt | 6 +- .../download/manga/OfflineMangaFragment.kt | 43 ++++--- .../download/novel/NovelDownloaderService.kt | 6 +- ...Service.kt => ExoplayerDownloadService.kt} | 2 +- .../ani/dantotsu/download/video/Helper.kt | 90 ++++++++++++--- .../ani/dantotsu/media/anime/ExoplayerView.kt | 64 ++++++++--- .../dantotsu/media/manga/MangaReadFragment.kt | 12 +- .../dantotsu/media/novel/NovelReadFragment.kt | 14 +-- .../java/ani/dantotsu/parsers/AnimeSources.kt | 10 +- .../ani/dantotsu/parsers/AniyomiAdapter.kt | 15 +-- .../java/ani/dantotsu/parsers/BaseSources.kt | 13 +++ .../java/ani/dantotsu/parsers/MangaSources.kt | 3 - .../dantotsu/parsers/OfflineAnimeParser.kt | 106 ++++++++++++++++++ .../dantotsu/parsers/OfflineMangaParser.kt | 2 +- .../dantotsu/parsers/OfflineNovelParser.kt | 5 +- .../ani/dantotsu/parsers/VideoExtractor.kt | 6 +- 19 files changed, 402 insertions(+), 156 deletions(-) rename app/src/main/java/ani/dantotsu/download/video/{MyDownloadService.kt => ExoplayerDownloadService.kt} (91%) create mode 100644 app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23f4b20d..8ab6c3ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -273,7 +273,7 @@ android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="true" /> diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt index b3536ea4..d6247ec5 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt @@ -15,43 +15,43 @@ class DownloadsManager(private val context: Context) { private val gson = Gson() private val downloadsList = loadDownloads().toMutableList() - val mangaDownloads: List - get() = downloadsList.filter { it.type == Download.Type.MANGA } - val animeDownloads: List - get() = downloadsList.filter { it.type == Download.Type.ANIME } - val novelDownloads: List - get() = downloadsList.filter { it.type == Download.Type.NOVEL } + val mangaDownloadedTypes: List + get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA } + val animeDownloadedTypes: List + get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME } + val novelDownloadedTypes: List + get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL } private fun saveDownloads() { val jsonString = gson.toJson(downloadsList) prefs.edit().putString("downloads_key", jsonString).apply() } - private fun loadDownloads(): List { + private fun loadDownloads(): List { val jsonString = prefs.getString("downloads_key", null) return if (jsonString != null) { - val type = object : TypeToken>() {}.type + val type = object : TypeToken>() {}.type gson.fromJson(jsonString, type) } else { emptyList() } } - fun addDownload(download: Download) { - downloadsList.add(download) + fun addDownload(downloadedType: DownloadedType) { + downloadsList.add(downloadedType) saveDownloads() } - fun removeDownload(download: Download) { - downloadsList.remove(download) - removeDirectory(download) + fun removeDownload(downloadedType: DownloadedType) { + downloadsList.remove(downloadedType) + removeDirectory(downloadedType) saveDownloads() } - fun removeMedia(title: String, type: Download.Type) { - val subDirectory = if (type == Download.Type.MANGA) { + fun removeMedia(title: String, type: DownloadedType.Type) { + val subDirectory = if (type == DownloadedType.Type.MANGA) { "Manga" - } else if (type == Download.Type.ANIME) { + } else if (type == DownloadedType.Type.ANIME) { "Anime" } else { "Novel" @@ -76,16 +76,16 @@ class DownloadsManager(private val context: Context) { } private fun cleanDownloads() { - cleanDownload(Download.Type.MANGA) - cleanDownload(Download.Type.ANIME) - cleanDownload(Download.Type.NOVEL) + cleanDownload(DownloadedType.Type.MANGA) + cleanDownload(DownloadedType.Type.ANIME) + cleanDownload(DownloadedType.Type.NOVEL) } - private fun cleanDownload(type: Download.Type) { + private fun cleanDownload(type: DownloadedType.Type) { // remove all folders that are not in the downloads list - val subDirectory = if (type == Download.Type.MANGA) { + val subDirectory = if (type == DownloadedType.Type.MANGA) { "Manga" - } else if (type == Download.Type.ANIME) { + } else if (type == DownloadedType.Type.ANIME) { "Anime" } else { "Novel" @@ -94,18 +94,18 @@ class DownloadsManager(private val context: Context) { context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$subDirectory" ) - val downloadsSubList = if (type == Download.Type.MANGA) { - mangaDownloads - } else if (type == Download.Type.ANIME) { - animeDownloads + val downloadsSubLists = if (type == DownloadedType.Type.MANGA) { + mangaDownloadedTypes + } else if (type == DownloadedType.Type.ANIME) { + animeDownloadedTypes } else { - novelDownloads + novelDownloadedTypes } if (directory.exists()) { val files = directory.listFiles() if (files != null) { for (file in files) { - if (!downloadsSubList.any { it.title == file.name }) { + if (!downloadsSubLists.any { it.title == file.name }) { val deleted = file.deleteRecursively() } } @@ -122,7 +122,7 @@ class DownloadsManager(private val context: Context) { } } - fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List) //for debugging + fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List) //for debugging { val jsonString = gson.toJson(downloadsList) val file = File( @@ -138,25 +138,33 @@ class DownloadsManager(private val context: Context) { file.writeText(jsonString) } - fun queryDownload(download: Download): Boolean { - return downloadsList.contains(download) + fun queryDownload(downloadedType: DownloadedType): Boolean { + return downloadsList.contains(downloadedType) } - private fun removeDirectory(download: Download) { - val directory = if (download.type == Download.Type.MANGA) { + fun queryDownload(title: String, chapter: String, type: DownloadedType.Type? = null): Boolean { + return if (type == null) { + downloadsList.any { it.title == title && it.chapter == chapter } + } else { + downloadsList.any { it.title == title && it.chapter == chapter && it.type == type } + } + } + + private fun removeDirectory(downloadedType: DownloadedType) { + val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Manga/${download.title}/${download.chapter}" + "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" ) - } else if (download.type == Download.Type.ANIME) { + } else if (downloadedType.type == DownloadedType.Type.ANIME) { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Anime/${download.title}/${download.chapter}" + "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" ) } else { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Novel/${download.title}/${download.chapter}" + "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" ) } @@ -173,26 +181,26 @@ class DownloadsManager(private val context: Context) { } } - fun exportDownloads(download: Download) { //copies to the downloads folder available to the user - val directory = if (download.type == Download.Type.MANGA) { + fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user + val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Manga/${download.title}/${download.chapter}" + "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" ) - } else if (download.type == Download.Type.ANIME) { + } else if (downloadedType.type == DownloadedType.Type.ANIME) { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Anime/${download.title}/${download.chapter}" + "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" ) } else { File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Novel/${download.title}/${download.chapter}" + "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" ) } val destination = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/${download.title}/${download.chapter}" + "Dantotsu/${downloadedType.title}/${downloadedType.chapter}" ) if (directory.exists()) { val copied = directory.copyRecursively(destination, true) @@ -206,10 +214,10 @@ class DownloadsManager(private val context: Context) { } } - fun purgeDownloads(type: Download.Type) { - val directory = if (type == Download.Type.MANGA) { + fun purgeDownloads(type: DownloadedType.Type) { + val directory = if (type == DownloadedType.Type.MANGA) { File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga") - } else if (type == Download.Type.ANIME) { + } else if (type == DownloadedType.Type.ANIME) { File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime") } else { File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel") @@ -237,7 +245,7 @@ class DownloadsManager(private val context: Context) { } -data class Download(val title: String, val chapter: String, val type: Type) : Serializable { +data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable { enum class Type { MANGA, ANIME, diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index 256e9577..1519f8a8 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.pm.ServiceInfo -import android.graphics.Bitmap import android.os.Build import android.os.Environment import android.os.IBinder @@ -18,15 +17,15 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.offline.Download +import androidx.media3.exoplayer.offline.DownloadManager import androidx.media3.exoplayer.offline.DownloadService import ani.dantotsu.R import ani.dantotsu.currActivity -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager -import ani.dantotsu.download.anime.AnimeDownloaderService -import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.download.video.Helper -import ani.dantotsu.download.video.MyDownloadService +import ani.dantotsu.download.video.ExoplayerDownloadService import ani.dantotsu.logger import ani.dantotsu.media.Media import ani.dantotsu.media.anime.AnimeWatchFragment @@ -44,14 +43,11 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapterImpl import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -161,7 +157,7 @@ class AnimeDownloaderService : Service() { val url = AnimeServiceDataSingleton.downloadQueue.find { it.getTaskName() == taskName }?.video?.file?.url ?: "" DownloadService.sendRemoveDownload( this@AnimeDownloaderService, - MyDownloadService::class.java, + ExoplayerDownloadService::class.java, url, false ) @@ -220,16 +216,28 @@ class AnimeDownloaderService : Service() { } saveMediaInfo(task) - downloadsManager.addDownload( - Download( - task.title, - task.episode, - Download.Type.ANIME, - ) + var continueDownload = false + downloadManager.addListener( + object : androidx.media3.exoplayer.offline.DownloadManager.Listener { + override fun onDownloadChanged( + downloadManager: DownloadManager, + download: Download, + finalException: Exception? + ) { + continueDownload = true + } + } ) + //set an async timeout of 30 seconds before setting continueDownload to true + launch { + kotlinx.coroutines.delay(30000) + continueDownload = true + } + + // periodically check if the download is complete - while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null) { + while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null || continueDownload == false) { val download = downloadManager.downloadIndex.getDownload(task.video.file.url) if (download != null) { if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) { @@ -251,6 +259,13 @@ class AnimeDownloaderService : Service() { task.getTaskName(), task.video.file.url ).apply() + downloadsManager.addDownload( + DownloadedType( + task.title, + task.episode, + DownloadedType.Type.ANIME, + ) + ) broadcastDownloadFinished(task.getTaskName()) break } @@ -284,9 +299,11 @@ class AnimeDownloaderService : Service() { GlobalScope.launch(Dispatchers.IO) { val directory = File( getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Anime/${task.title}" + "${DownloadsManager.animeLocation}/${task.title}" ) + val episodeDirectory = File(directory, task.episode) if (!directory.exists()) directory.mkdirs() + if (!episodeDirectory.exists()) episodeDirectory.mkdirs() val file = File(directory, "media.json") val gson = GsonBuilder() @@ -305,6 +322,9 @@ class AnimeDownloaderService : Service() { if (media != null) { media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") } media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") } + if (task.episodeImage != null) { + downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg") + } val jsonString = gson.toJson(media) withContext(Dispatchers.Main) { @@ -395,6 +415,7 @@ class AnimeDownloaderService : Service() { val video: Video, val subtitle: Subtitle? = null, val sourceMedia: Media? = null, + val episodeImage: String? = null, val retries: Int = 2, val simultaneousDownloads: Int = 2, ) { diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index fce89d3e..202a4b8f 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -18,7 +18,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import ani.dantotsu.R -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.logger import ani.dantotsu.media.Media @@ -246,10 +246,10 @@ class MangaDownloaderService : Service() { saveMediaInfo(task) downloadsManager.addDownload( - Download( + DownloadedType( task.title, task.chapter, - Download.Type.MANGA + DownloadedType.Type.MANGA ) ) broadcastDownloadFinished(task.chapter) diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index d405583c..1b7c031b 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.download.manga import android.animation.ObjectAnimator import android.content.Context -import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Build @@ -23,20 +22,16 @@ import android.widget.GridView import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView -import androidx.core.app.ActivityCompat.recreate import androidx.fragment.app.Fragment import ani.dantotsu.R -import ani.dantotsu.Refresh import ani.dantotsu.currActivity import ani.dantotsu.currContext -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager -import ani.dantotsu.initActivity import ani.dantotsu.logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.setSafeOnClickListener -import ani.dantotsu.settings.SettingsActivity import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight @@ -168,8 +163,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { // Get the OfflineMangaModel that was clicked val item = adapter.getItem(position) as OfflineMangaModel val media = - downloadManager.mangaDownloads.firstOrNull { it.title == item.title } - ?: downloadManager.novelDownloads.firstOrNull { it.title == item.title } + downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title } + ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title } media?.let { startActivity( Intent(requireContext(), MediaDetailsActivity::class.java) @@ -184,10 +179,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { gridView.setOnItemLongClickListener { parent, view, position, id -> // Get the OfflineMangaModel that was clicked val item = adapter.getItem(position) as OfflineMangaModel - val type: Download.Type = if (downloadManager.mangaDownloads.any { it.title == item.title }) { - Download.Type.MANGA + val type: DownloadedType.Type = if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) { + DownloadedType.Type.MANGA } else { - Download.Type.NOVEL + DownloadedType.Type.NOVEL } // Alert dialog to confirm deletion val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) @@ -292,19 +287,19 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { private fun getDownloads() { downloads = listOf() - val mangaTitles = downloadManager.mangaDownloads.map { it.title }.distinct() + val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val newMangaDownloads = mutableListOf() for (title in mangaTitles) { - val _downloads = downloadManager.mangaDownloads.filter { it.title == title } + val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title } val download = _downloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newMangaDownloads += offlineMangaModel } downloads = newMangaDownloads - val novelTitles = downloadManager.novelDownloads.map { it.title }.distinct() + val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val newNovelDownloads = mutableListOf() for (title in novelTitles) { - val _downloads = downloadManager.novelDownloads.filter { it.title == title } + val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title } val download = _downloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newNovelDownloads += offlineMangaModel @@ -313,17 +308,17 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } - private fun getMedia(download: Download): Media? { - val type = if (download.type == Download.Type.MANGA) { + private fun getMedia(downloadedType: DownloadedType): Media? { + val type = if (downloadedType.type == DownloadedType.Type.MANGA) { "Manga" - } else if (download.type == Download.Type.ANIME) { + } else if (downloadedType.type == DownloadedType.Type.ANIME) { "Anime" } else { "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/$type/${download.title}" + "Dantotsu/$type/${downloadedType.title}" ) //load media.json and convert to media class with gson return try { @@ -343,23 +338,23 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } } - private fun loadOfflineMangaModel(download: Download): OfflineMangaModel { - val type = if (download.type == Download.Type.MANGA) { + private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { + val type = if (downloadedType.type == DownloadedType.Type.MANGA) { "Manga" - } else if (download.type == Download.Type.ANIME) { + } else if (downloadedType.type == DownloadedType.Type.ANIME) { "Anime" } else { "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/$type/${download.title}" + "Dantotsu/$type/${downloadedType.title}" ) //load media.json and convert to media class with gson try { val media = File(directory, "media.json") val mediaJson = media.readText() - val mediaModel = getMedia(download)!! + val mediaModel = getMedia(downloadedType)!! val cover = File(directory, "cover.jpg") val coverUri: Uri? = if (cover.exists()) { Uri.fromFile(cover) diff --git a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt index e987cd25..d729ef57 100644 --- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt @@ -17,7 +17,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import ani.dantotsu.R -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.logger import ani.dantotsu.media.Media @@ -330,10 +330,10 @@ class NovelDownloaderService : Service() { saveMediaInfo(task) downloadsManager.addDownload( - Download( + DownloadedType( task.title, task.chapter, - Download.Type.NOVEL + DownloadedType.Type.NOVEL ) ) broadcastDownloadFinished(task.originalLink) diff --git a/app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt b/app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt similarity index 91% rename from app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt rename to app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt index 8c0d56d2..3d7a1802 100644 --- a/app/src/main/java/ani/dantotsu/download/video/MyDownloadService.kt +++ b/app/src/main/java/ani/dantotsu/download/video/ExoplayerDownloadService.kt @@ -11,7 +11,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler import ani.dantotsu.R @UnstableApi -class MyDownloadService : DownloadService(1, 2000, "download_service", R.string.downloads, 0) { +class ExoplayerDownloadService : DownloadService(1, 2000, "download_service", R.string.downloads, 0) { companion object { private const val JOB_ID = 1 private const val FOREGROUND_NOTIFICATION_ID = 1 diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index 056e96ee..4c10752d 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -3,6 +3,7 @@ package ani.dantotsu.download.video import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.AlertDialog import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -12,6 +13,7 @@ import android.util.Log import androidx.annotation.OptIn import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.getString import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.MimeTypes @@ -32,6 +34,8 @@ import androidx.media3.exoplayer.scheduler.Requirements import androidx.media3.ui.TrackSelectionDialogBuilder import ani.dantotsu.R import ani.dantotsu.defaultHeaders +import ani.dantotsu.download.DownloadedType +import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.logError @@ -50,7 +54,8 @@ import java.util.concurrent.* object Helper { - var simpleCache: SimpleCache? = null + private var simpleCache: SimpleCache? = null + @SuppressLint("UnsafeOptInUsageError") fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) { val dataSourceFactory = DataSource.Factory { @@ -96,18 +101,18 @@ object Helper { ) downloadHelper.prepare(object : DownloadHelper.Callback { override fun onPrepared(helper: DownloadHelper) { - TrackSelectionDialogBuilder( - context, "Select thingy", helper.getTracks(0).groups + /*TrackSelectionDialogBuilder( TODO: use this for subtitles + context, "Select Source", helper.getTracks(0).groups ) { _, overrides -> val params = TrackSelectionParameters.Builder(context) overrides.forEach { params.addOverride(it.value) } helper.addTrackSelection(0, params.build()) - MyDownloadService + ExoplayerDownloadService DownloadService.sendAddDownload( context, - MyDownloadService::class.java, + ExoplayerDownloadService::class.java, helper.getDownloadRequest(null), false ) @@ -117,6 +122,14 @@ object Helper { if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)" } build().show() + }*/ + helper.getDownloadRequest(null).let { + DownloadService.sendAddDownload( + context, + ExoplayerDownloadService::class.java, + it, + false + ) } } @@ -149,7 +162,7 @@ object Helper { } val threadPoolSize = Runtime.getRuntime().availableProcessors() val executorService = Executors.newFixedThreadPool(threadPoolSize) - val downloadManager = DownloadManager( + val downloadManager = DownloadManager( context, database, getSimpleCache(context), @@ -160,15 +173,15 @@ object Helper { Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW) maxParallelDownloads = 3 } - downloadManager.addListener( - object : DownloadManager.Listener { // Override methods of interest here. + downloadManager.addListener( //for testing + object : DownloadManager.Listener { override fun onDownloadChanged( downloadManager: DownloadManager, download: Download, finalException: Exception? ) { if (download.state == Download.STATE_COMPLETED) { - Log.e("Downloader", "Download Completed") + Log.e("Downloader", "Download Completed") } else if (download.state == Download.STATE_FAILED) { Log.e("Downloader", "Download Failed") } else if (download.state == Download.STATE_STOPPED) { @@ -199,6 +212,7 @@ object Helper { return downloadDirectory!! } + @OptIn(UnstableApi::class) fun startAnimeDownloadService( context: Context, title: String, @@ -224,16 +238,62 @@ object Helper { subtitle, sourceMedia ) - AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) - if (!AnimeServiceDataSingleton.isServiceRunning) { - val intent = Intent(context, AnimeDownloaderService::class.java) - ContextCompat.startForegroundService(context, intent) - AnimeServiceDataSingleton.isServiceRunning = true + val downloadsManger = Injekt.get() + val downloadCheck = downloadsManger + .queryDownload(title, episode, DownloadedType.Type.ANIME) + + if (downloadCheck) { + AlertDialog.Builder(context) + .setTitle("Download Exists") + .setMessage("A download for this episode already exists. Do you want to overwrite it?") + .setPositiveButton("Yes") { _, _ -> + DownloadService.sendRemoveDownload( + context, + ExoplayerDownloadService::class.java, + context.getSharedPreferences( + getString(context, R.string.anime_downloads), + Context.MODE_PRIVATE + ).getString( + downloadTask.getTaskName(), + "" + ) ?: "", + false + ) + context.getSharedPreferences( + getString(context, R.string.anime_downloads), + Context.MODE_PRIVATE + ).edit() + .remove(downloadTask.getTaskName()) + .apply() + downloadsManger.removeDownload( + DownloadedType( + title, + episode, + DownloadedType.Type.ANIME + ) + ) + AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) + if (!AnimeServiceDataSingleton.isServiceRunning) { + val intent = Intent(context, AnimeDownloaderService::class.java) + ContextCompat.startForegroundService(context, intent) + AnimeServiceDataSingleton.isServiceRunning = true + } + } + .setNegativeButton("No") { _, _ -> } + .show() + } else { + AnimeServiceDataSingleton.downloadQueue.offer(downloadTask) + if (!AnimeServiceDataSingleton.isServiceRunning) { + val intent = Intent(context, AnimeDownloaderService::class.java) + ContextCompat.startForegroundService(context, intent) + AnimeServiceDataSingleton.isServiceRunning = true + } } } - @OptIn(UnstableApi::class) private fun getSimpleCache(context: Context): SimpleCache { + @OptIn(UnstableApi::class) + fun getSimpleCache(context: Context): SimpleCache { return if (simpleCache == null) { val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY) val database = Injekt.get() diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 253080e2..8643d9de 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -4,6 +4,7 @@ import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.AlertDialog import android.app.Dialog +import android.app.DownloadManager import android.app.PictureInPictureParams import android.app.PictureInPictureUiState import android.content.ActivityNotFoundException @@ -97,7 +98,9 @@ import kotlin.math.min import kotlin.math.roundToInt import androidx.media3.cast.SessionAvailabilityListener import androidx.media3.cast.CastPlayer +import androidx.media3.exoplayer.offline.Download import androidx.mediarouter.app.MediaRouteButton +import ani.dantotsu.download.video.Helper import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext @@ -150,6 +153,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL private var orientationListener: OrientationEventListener? = null + private var downloadId: String? = null + companion object { var initialized = false lateinit var media: Media @@ -475,7 +480,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL if (isInitialized) { isPlayerPlaying = exoPlayer.isPlaying (exoPlay.drawable as Animatable?)?.start() - if (isPlayerPlaying || castPlayer.isPlaying ) { + if (isPlayerPlaying || castPlayer.isPlaying) { Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay) exoPlayer.pause() castPlayer.pause() @@ -1115,7 +1120,21 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL if (settings.cursedSpeeds) arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f) else - arrayOf(0.25f, 0.33f, 0.5f, 0.66f, 0.75f, 1f, 1.15f, 1.25f, 1.33f, 1.5f, 1.66f, 1.75f, 2f) + arrayOf( + 0.25f, + 0.33f, + 0.5f, + 0.66f, + 0.75f, + 1f, + 1.15f, + 1.25f, + 1.33f, + 1.5f, + 1.66f, + 1.75f, + 2f + ) val speedsName = speeds.map { "${it}x" }.toTypedArray() var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed @@ -1292,7 +1311,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL if (video?.format == VideoType.CONTAINER || (loadData("settings_download_manager") ?: 0) != 0 ) { - but.visibility = View.VISIBLE + //but.visibility = View.VISIBLE TODO: not sure if this is needed but.setOnClickListener { download(this, episode, animeTitle.text.toString()) } @@ -1317,8 +1336,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL dataSource } cacheFactory = CacheDataSource.Factory().apply { - setCache(simpleCache) + setCache(Helper.getSimpleCache(this@ExoplayerView)) setUpstreamDataSourceFactory(dataSourceFactory) + setCacheWriteDataSinkFactory(null) } val mimeType = when (video?.format) { @@ -1327,15 +1347,33 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL else -> MimeTypes.APPLICATION_MP4 } - val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType) - logger("url: ${video!!.file.url}") - logger("mimeType: $mimeType") + val downloadedMediaItem = if (ext.server.offline) { + val key = ext.server.name + downloadId = getSharedPreferences(getString(R.string.anime_downloads), MODE_PRIVATE) + .getString(key, null) + if (downloadId != null) { + Helper.downloadManager(this) + .downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem() + } else { + snackString("Download not found") + null + } + } else null - if (sub != null) { - val listofnotnullsubs = immutableListOf(sub).filterNotNull() - builder.setSubtitleConfigurations(listofnotnullsubs) + mediaItem = if (downloadedMediaItem == null) { + val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType) + logger("url: ${video!!.file.url}") + logger("mimeType: $mimeType") + + if (sub != null) { + val listofnotnullsubs = immutableListOf(sub).filterNotNull() + builder.setSubtitleConfigurations(listofnotnullsubs) + } + builder.build() + } else { + downloadedMediaItem } - mediaItem = builder.build() + //Source exoSource.setOnClickListener { @@ -1457,7 +1495,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL exoPlayer.release() VideoCache.release() mediaSession?.release() - if(DiscordServiceRunningSingleton.running) { + if (DiscordServiceRunningSingleton.running) { val stopIntent = Intent(this, DiscordService::class.java) DiscordServiceRunningSingleton.running = false stopService(stopIntent) @@ -1594,7 +1632,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL if (isInitialized) { if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) { preloading = true - nextEpisode(false) { i -> + nextEpisode(false) { i -> //TODO: make sure this works for offline episodes val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode val selected = media.selected ?: return@nextEpisode lifecycleScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 9b70ef7f..241c979a 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -29,7 +29,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.FragmentAnimeWatchBinding -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.manga.MangaDownloaderService import ani.dantotsu.download.manga.MangaServiceDataSingleton @@ -166,7 +166,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { chapterAdapter = MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this) - for (download in downloadManager.mangaDownloads) { + for (download in downloadManager.mangaDownloadedTypes) { chapterAdapter.stopDownload(download.chapter) } @@ -482,10 +482,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { fun onMangaChapterRemoveDownloadClick(i: String) { downloadManager.removeDownload( - Download( + DownloadedType( media.nameMAL ?: media.nameRomaji, i, - Download.Type.MANGA + DownloadedType.Type.MANGA ) ) chapterAdapter.deleteDownload(i) @@ -500,10 +500,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { // Remove the download from the manager and update the UI downloadManager.removeDownload( - Download( + DownloadedType( media.nameMAL ?: media.nameRomaji, i, - Download.Type.MANGA + DownloadedType.Type.MANGA ) ) chapterAdapter.purgeDownload(i) diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt index 7154412a..b3e14ed2 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.databinding.FragmentAnimeWatchBinding -import ani.dantotsu.download.Download +import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.novel.NovelDownloaderService import ani.dantotsu.download.novel.NovelServiceDataSingleton @@ -92,10 +92,10 @@ class NovelReadFragment : Fragment(), override fun downloadedCheckWithStart(novel: ShowResponse): Boolean { val downloadsManager = Injekt.get() if (downloadsManager.queryDownload( - Download( + DownloadedType( media.nameMAL ?: media.nameRomaji, novel.name, - Download.Type.NOVEL + DownloadedType.Type.NOVEL ) ) ) { @@ -124,10 +124,10 @@ class NovelReadFragment : Fragment(), override fun downloadedCheck(novel: ShowResponse): Boolean { val downloadsManager = Injekt.get() return downloadsManager.queryDownload( - Download( + DownloadedType( media.nameMAL ?: media.nameRomaji, novel.name, - Download.Type.NOVEL + DownloadedType.Type.NOVEL ) ) } @@ -135,10 +135,10 @@ class NovelReadFragment : Fragment(), override fun deleteDownload(novel: ShowResponse) { val downloadsManager = Injekt.get() downloadsManager.removeDownload( - Download( + DownloadedType( media.nameMAL ?: media.nameRomaji, novel.name, - Download.Type.NOVEL + DownloadedType.Type.NOVEL ) ) } diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index c48a4b3e..dd3faaef 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -12,11 +12,17 @@ object AnimeSources : WatchSources() { suspend fun init(fromExtensions: StateFlow>) { // Initialize with the first value from StateFlow val initialExtensions = fromExtensions.first() - list = createParsersFromExtensions(initialExtensions) + list = createParsersFromExtensions(initialExtensions) + Lazier( + { OfflineAnimeParser() }, + "Downloaded" + ) // Update as StateFlow emits new values fromExtensions.collect { extensions -> - list = createParsersFromExtensions(extensions) + list = createParsersFromExtensions(extensions) + Lazier( + { OfflineAnimeParser() }, + "Downloaded" + ) } } diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 27aece5f..08c69b55 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -616,17 +616,18 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { val fileName = queryPairs.find { it.first == "file" }?.second ?: "" format = getVideoType(fileName) - if (format == null) { - val networkHelper = Injekt.get() - format = headRequest(videoUrl, networkHelper) - } + // this solves a problem no one has, so I'm commenting it out for now + //if (format == null) { + // val networkHelper = Injekt.get() + // format = headRequest(videoUrl, networkHelper) + //} } - // If the format is still undetermined, log an error or handle it appropriately + // If the format is still undetermined, log an error if (format == null) { logger("Unknown video format: $videoUrl") - FirebaseCrashlytics.getInstance() - .recordException(Exception("Unknown video format: $videoUrl")) + //FirebaseCrashlytics.getInstance() + // .recordException(Exception("Unknown video format: $videoUrl")) format = VideoType.CONTAINER } val headersMap: Map = diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt index f8535772..95fd4d63 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt @@ -46,6 +46,19 @@ abstract class WatchSources : BaseSources() { sEpisode = it.sEpisode ) } + } else if (parser is OfflineAnimeParser) { + parser.loadEpisodes(showLink, extra, SAnime.create()).forEach { + map[it.number] = Episode( + it.number, + it.link, + it.title, + it.description, + it.thumbnail, + it.isFiller, + extra = it.extra, + sEpisode = it.sEpisode + ) + } } } return map diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt index 0f8a5642..3709ddaa 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt @@ -7,9 +7,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first object MangaSources : MangaReadSources() { - // Instantiate the static parser - private val offlineMangaParser by lazy { OfflineMangaParser() } - override var list: List> = emptyList() suspend fun init(fromExtensions: StateFlow>) { diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt new file mode 100644 index 00000000..dca2199e --- /dev/null +++ b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt @@ -0,0 +1,106 @@ +package ani.dantotsu.parsers + +import android.os.Environment +import ani.dantotsu.currContext +import ani.dantotsu.download.DownloadsManager +import ani.dantotsu.logger +import ani.dantotsu.media.anime.AnimeNameAdapter +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl +import me.xdrop.fuzzywuzzy.FuzzySearch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.File + +class OfflineAnimeParser : AnimeParser() { + private val downloadManager = Injekt.get() + + override val name = "Offline" + override val saveName = "Offline" + override val hostUrl = "Offline" + override val isDubAvailableSeparately = false + override val isNSFW = false + + override suspend fun loadEpisodes( + animeLink: String, + extra: Map?, + sAnime: SAnime + ): List { + val directory = File( + currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "${DownloadsManager.animeLocation}/$animeLink" + ) + //get all of the folder names and add them to the list + val episodes = mutableListOf() + if (directory.exists()) { + directory.listFiles()?.forEach { + if (it.isDirectory) { + val episode = Episode( + it.name, + "$animeLink - ${it.name}", + it.name, + null, + null, + sEpisode = SEpisodeImpl() + ) + episodes.add(episode) + } + } + episodes.sortBy { AnimeNameAdapter.findEpisodeNumber(it.number) } + return episodes + } + return emptyList() + } + + override suspend fun loadVideoServers( + episodeLink: String, + extra: Map?, + sEpisode: SEpisode + ): List { + return listOf( + VideoServer( + episodeLink, + offline = true + ) + ) + } + + + override suspend fun search(query: String): List { + val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() + val returnTitles: MutableList = mutableListOf() + for (title in titles) { + if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { + returnTitles.add(title) + } + } + val returnList: MutableList = mutableListOf() + for (title in returnTitles) { + returnList.add(ShowResponse(title, title, title)) + } + return returnList + } + + override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor { + return OfflineVideoExtractor(server) + } + +} + +class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() { + override val server: VideoServer + get() = videoServer + + override suspend fun extract(): VideoContainer { + val sublist = emptyList() + //we need to return a "fake" video so that the app doesn't crash + val video = Video( + null, + VideoType.CONTAINER, + "", + ) + return VideoContainer(listOf(video), sublist) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt index 218a0f9a..deffb420 100644 --- a/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt @@ -76,7 +76,7 @@ class OfflineMangaParser : MangaParser() { } override suspend fun search(query: String): List { - val titles = downloadManager.mangaDownloads.map { it.title }.distinct() + val titles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val returnTitles: MutableList = mutableListOf() for (title in titles) { if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt index ca47b50a..fece57e2 100644 --- a/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/OfflineNovelParser.kt @@ -3,10 +3,7 @@ package ani.dantotsu.parsers import android.os.Environment import ani.dantotsu.currContext import ani.dantotsu.download.DownloadsManager -import ani.dantotsu.logger import ani.dantotsu.media.manga.MangaNameAdapter -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga import me.xdrop.fuzzywuzzy.FuzzySearch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -53,7 +50,7 @@ class OfflineNovelParser: NovelParser() { } override suspend fun search(query: String): List { - val titles = downloadManager.novelDownloads.map { it.title }.distinct() + val titles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val returnTitles: MutableList = mutableListOf() for (title in titles) { if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { diff --git a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt index ce56182c..c98b8a42 100644 --- a/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt +++ b/app/src/main/java/ani/dantotsu/parsers/VideoExtractor.kt @@ -57,11 +57,15 @@ data class VideoServer( val name: String, val embed: FileUrl, val extraData: Map? = null, - val video: eu.kanade.tachiyomi.animesource.model.Video? = null + val video: eu.kanade.tachiyomi.animesource.model.Video? = null, + val offline: Boolean = false ) : Serializable { constructor(name: String, embedUrl: String, extraData: Map? = null) : this(name, FileUrl(embedUrl), extraData) + constructor(name: String, offline: Boolean) + : this(name, FileUrl(""), null, null, offline) + constructor( name: String, embedUrl: String,