fix: download name comparison

This commit is contained in:
rebelonion 2024-04-05 17:44:59 -05:00
parent f6c7b09d9b
commit 5fcbfeb3db
6 changed files with 91 additions and 43 deletions

View file

@ -3,7 +3,7 @@ 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.DownloadsManager.Companion.findValidName 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
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
@ -12,7 +12,6 @@ import ani.dantotsu.util.Logger
import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.deleteRecursively import com.anggrayudi.storage.file.deleteRecursively
import com.anggrayudi.storage.file.findFolder import com.anggrayudi.storage.file.findFolder
import com.anggrayudi.storage.file.moveFileTo
import com.anggrayudi.storage.file.moveFolderTo import com.anggrayudi.storage.file.moveFolderTo
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
@ -20,6 +19,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.xdrop.fuzzywuzzy.FuzzySearch
import java.io.Serializable import java.io.Serializable
class DownloadsManager(private val context: Context) { class DownloadsManager(private val context: Context) {
@ -127,7 +127,12 @@ class DownloadsManager(private val context: Context) {
} }
} }
fun moveDownloadsDir(context: Context, oldUri: Uri, newUri: Uri, finished: (Boolean, String) -> Unit) { fun moveDownloadsDir(
context: Context,
oldUri: Uri,
newUri: Uri,
finished: (Boolean, String) -> Unit
) {
try { try {
if (oldUri == newUri) { if (oldUri == newUri) {
finished(false, "Source and destination are the same") finished(false, "Source and destination are the same")
@ -141,18 +146,41 @@ class DownloadsManager(private val context: Context) {
DocumentFile.fromTreeUri(context, newUri) ?: throw Exception("New base is null") DocumentFile.fromTreeUri(context, newUri) ?: throw Exception("New base is null")
val folder = val folder =
oldBase.findFolder(BASE_LOCATION) ?: throw Exception("Base folder not found") oldBase.findFolder(BASE_LOCATION) ?: throw Exception("Base folder not found")
folder.moveFolderTo(context, newBase, false, BASE_LOCATION, object: folder.moveFolderTo(context, newBase, false, BASE_LOCATION, object :
FolderCallback() { FolderCallback() {
override fun onFailed(errorCode: ErrorCode) { override fun onFailed(errorCode: ErrorCode) {
when (errorCode) { when (errorCode) {
ErrorCode.CANCELED -> finished(false, "Move canceled") ErrorCode.CANCELED -> finished(false, "Move canceled")
ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> finished(false, "Cannot create file in target") ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> finished(
ErrorCode.INVALID_TARGET_FOLDER -> finished(true, "Invalid target folder") // seems to still work false,
ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> finished(false, "No space left on target path") "Cannot create file in target"
)
ErrorCode.INVALID_TARGET_FOLDER -> finished(
true,
"Invalid target folder"
) // seems to still work
ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> finished(
false,
"No space left on target path"
)
ErrorCode.UNKNOWN_IO_ERROR -> finished(false, "Unknown IO error") ErrorCode.UNKNOWN_IO_ERROR -> finished(false, "Unknown IO error")
ErrorCode.SOURCE_FOLDER_NOT_FOUND -> finished(false, "Source folder not found") ErrorCode.SOURCE_FOLDER_NOT_FOUND -> finished(
ErrorCode.STORAGE_PERMISSION_DENIED -> finished(false, "Storage permission denied") false,
ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> finished(false, "Target folder cannot have same path with source folder") "Source folder not found"
)
ErrorCode.STORAGE_PERMISSION_DENIED -> finished(
false,
"Storage permission denied"
)
ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> finished(
false,
"Target folder cannot have same path with source folder"
)
else -> finished(false, "Failed to move downloads: $errorCode") else -> finished(false, "Failed to move downloads: $errorCode")
} }
Logger.log("Failed to move downloads: $errorCode") Logger.log("Failed to move downloads: $errorCode")
@ -189,7 +217,7 @@ class DownloadsManager(private val context: Context) {
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.title)?.findFolder(downloadedType.chapter)
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) {
val deleted = directory.deleteRecursively(context, false) val deleted = directory.deleteRecursively(context, false)
@ -226,11 +254,7 @@ class DownloadsManager(private val context: Context) {
private const val MANGA_SUB_LOCATION = "Manga" private const val MANGA_SUB_LOCATION = "Manga"
private const val ANIME_SUB_LOCATION = "Anime" private const val ANIME_SUB_LOCATION = "Anime"
private const val NOVEL_SUB_LOCATION = "Novel" private const val NOVEL_SUB_LOCATION = "Novel"
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
fun String?.findValidName(): String {
return this?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
}
/** /**
* Get and create a base directory for the given type * Get and create a base directory for the given type
@ -238,7 +262,6 @@ class DownloadsManager(private val context: Context) {
* @param type the type of media * @param type the type of media
* @return the base directory * @return the base directory
*/ */
private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? { private fun getBaseDirectory(context: Context, type: MediaType): DocumentFile? {
val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir)) val baseDirectory = Uri.parse(PrefManager.getVal<String>(PrefName.DownloadsDir))
if (baseDirectory == Uri.EMPTY) return null if (baseDirectory == Uri.EMPTY) return null
@ -283,7 +306,12 @@ class DownloadsManager(private val context: Context) {
} }
} }
fun getDirSize(context: Context, type: MediaType, title: String, chapter: String? = null): Long { fun getDirSize(
context: Context,
type: MediaType,
title: String,
chapter: String? = null
): Long {
val directory = getSubDirectory(context, type, false, title, chapter) ?: return 0 val directory = getSubDirectory(context, type, false, title, chapter) ?: return 0
var size = 0L var size = 0L
directory.listFiles().forEach { directory.listFiles().forEach {
@ -303,7 +331,25 @@ class DownloadsManager(private val context: Context) {
} }
} }
private const val RATIO_THRESHOLD = 95
fun Media.compareName(name: String): Boolean {
val mainName = mainName().findValidName().lowercase()
val ratio = FuzzySearch.ratio(mainName, name.lowercase())
return ratio > RATIO_THRESHOLD
} }
fun String.compareName(name: String): Boolean {
val mainName = findValidName().lowercase()
val compareName = name.findValidName().lowercase()
val ratio = FuzzySearch.ratio(mainName, compareName)
return ratio > RATIO_THRESHOLD
}
}
}
private const val RESERVED_CHARS = "|\\?*<\":>+[]/'"
private fun String?.findValidName(): String {
return this?.filterNot { RESERVED_CHARS.contains(it) } ?: ""
} }
data class DownloadedType( data class DownloadedType(

View file

@ -33,7 +33,7 @@ import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
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.findValidName import ani.dantotsu.download.DownloadsManager.Companion.compareName
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
@ -175,7 +175,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 == item.title.findValidName() } downloadManager.animeDownloadedTypes.firstOrNull { it.title.compareName(item.title) }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {
val mediaModel = getMedia(it) val mediaModel = getMedia(it)

View file

@ -168,7 +168,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 media = val media =
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title } downloadManager.mangaDownloadedTypes.firstOrNull { it.title.contains(item.title) }
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
media?.let { media?.let {
lifecycleScope.launch { lifecycleScope.launch {

View file

@ -14,7 +14,6 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
@ -25,7 +24,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -35,14 +33,14 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
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.findValidName import ani.dantotsu.download.DownloadsManager.Companion.compareName
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.dp import ani.dantotsu.dp
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.subscription.SubscriptionHelper import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
@ -425,17 +423,27 @@ class AnimeWatchFragment : Fragment() {
} }
fun onAnimeEpisodeDownloadClick(i: String) { fun onAnimeEpisodeDownloadClick(i: String) {
activity?.let{ activity?.let {
if (!hasDirAccess(it)) { if (!hasDirAccess(it)) {
(it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success -> (it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success ->
if (success) { if (success) {
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true) model.onEpisodeClick(
media,
i,
requireActivity().supportFragmentManager,
isDownload = true
)
} else { } else {
snackString("Permission is required to download") snackString("Permission is required to download")
} }
} }
} else { } else {
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true) model.onEpisodeClick(
media,
i,
requireActivity().supportFragmentManager,
isDownload = true
)
} }
} }
} }
@ -472,10 +480,6 @@ class AnimeWatchFragment : Fragment() {
) )
) { ) {
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i) val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
val id = PrefManager.getAnimeDownloadPreferences().getString(
taskName,
""
) ?: ""
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply() PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
episodeAdapter.deleteDownload(i) episodeAdapter.deleteDownload(i)
} }
@ -542,7 +546,7 @@ 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 (download.title == media.mainName().findValidName()) { if (media.compareName(download.title)) {
episodeAdapter.stopDownload(download.chapter) episodeAdapter.stopDownload(download.chapter)
} }
} }

View file

@ -16,7 +16,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -35,7 +34,7 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
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.findValidName import ani.dantotsu.download.DownloadsManager.Companion.compareName
import ani.dantotsu.download.manga.MangaDownloaderService import ani.dantotsu.download.manga.MangaDownloaderService
import ani.dantotsu.download.manga.MangaServiceDataSingleton import ani.dantotsu.download.manga.MangaServiceDataSingleton
import ani.dantotsu.dp import ani.dantotsu.dp
@ -194,7 +193,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
) )
for (download in downloadManager.mangaDownloadedTypes) { for (download in downloadManager.mangaDownloadedTypes) {
if (download.title == media.mainName().findValidName()) { if (media.compareName(download.title)) {
chapterAdapter.stopDownload(download.chapter) chapterAdapter.stopDownload(download.chapter)
} }
} }

View file

@ -1,8 +1,6 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.app.Application import android.app.Application
import android.net.Uri
import android.os.Environment
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
@ -10,13 +8,13 @@ import ani.dantotsu.download.anime.AnimeDownloaderService.AnimeDownloadTask.Comp
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
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
import java.io.File
import java.util.Locale import java.util.Locale
class OfflineAnimeParser : AnimeParser() { class OfflineAnimeParser : AnimeParser() {
@ -80,6 +78,7 @@ class OfflineAnimeParser : AnimeParser() {
val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
val returnTitles: MutableList<String> = mutableListOf() val returnTitles: MutableList<String> = mutableListOf()
for (title in titles) { for (title in titles) {
Logger.log("Comparing $title to $query")
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) { if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
returnTitles.add(title) returnTitles.add(title)
} }
@ -112,7 +111,7 @@ class OfflineAnimeParser : AnimeParser() {
} }
class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() { class OfflineVideoExtractor(private val videoServer: VideoServer) : VideoExtractor() {
override val server: VideoServer override val server: VideoServer
get() = videoServer get() = videoServer
@ -132,7 +131,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
private fun getSubtitle(title: String, episode: String): List<Subtitle>? { private fun getSubtitle(title: String, episode: String): List<Subtitle>? {
currContext()?.let { currContext()?.let {
DownloadsManager.getSubDirectory( getSubDirectory(
it, it,
MediaType.ANIME, MediaType.ANIME,
false, false,
@ -144,7 +143,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
Subtitle( Subtitle(
"Downloaded Subtitle", "Downloaded Subtitle",
file.uri.toString(), file.uri.toString(),
determineSubtitletype(file.name ?: "") determineSubtitleType(file.name ?: "")
) )
) )
} }
@ -153,7 +152,7 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
return null return null
} }
fun determineSubtitletype(url: String): SubtitleType { private fun determineSubtitleType(url: String): SubtitleType {
return when { return when {
url.lowercase(Locale.ROOT).endsWith("ass") -> SubtitleType.ASS url.lowercase(Locale.ROOT).endsWith("ass") -> SubtitleType.ASS
url.lowercase(Locale.ROOT).endsWith("vtt") -> SubtitleType.VTT url.lowercase(Locale.ROOT).endsWith("vtt") -> SubtitleType.VTT