feat: novel to new system | load freezing

This commit is contained in:
rebelonion
2024-04-04 01:02:37 -05:00
parent bf73bbb98b
commit 9e6f71feb2
5 changed files with 206 additions and 141 deletions

View File

@@ -4,7 +4,6 @@ package ani.dantotsu.download.anime
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.TypedValue import android.util.TypedValue
@@ -25,6 +24,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.bottomBar import ani.dantotsu.bottomBar
@@ -33,6 +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.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
@@ -56,9 +57,13 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SChapterImpl import eu.kanade.tachiyomi.source.model.SChapterImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
@@ -67,6 +72,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
private lateinit var gridView: GridView private lateinit var gridView: GridView
private lateinit var adapter: OfflineAnimeAdapter private lateinit var adapter: OfflineAnimeAdapter
private lateinit var total: TextView private lateinit var total: TextView
private var downloadsJob: Job = Job()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@@ -113,10 +119,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
}) })
var style: Int = PrefManager.getVal(PrefName.OfflineView) var style: Int = PrefManager.getVal(PrefName.OfflineView)
val layoutList = view.findViewById<ImageView>(R.id.downloadedList) val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid) val layoutCompact = view.findViewById<ImageView>(R.id.downloadedGrid)
var selected = when (style) { var selected = when (style) {
0 -> layoutList 0 -> layoutList
1 -> layoutcompact 1 -> layoutCompact
else -> layoutList else -> layoutList
} }
selected.alpha = 1f selected.alpha = 1f
@@ -137,7 +143,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
grid() grid()
} }
layoutcompact.setOnClickListener { layoutCompact.setOnClickListener {
selected(it as ImageView) selected(it as ImageView)
style = 1 style = 1
PrefManager.setVal(PrefName.OfflineView, style) PrefManager.setVal(PrefName.OfflineView, style)
@@ -157,11 +163,11 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun grid() { private fun grid() {
gridView.visibility = View.VISIBLE gridView.visibility = View.VISIBLE
getDownloads()
val fadeIn = AlphaAnimation(0f, 1f) val fadeIn = AlphaAnimation(0f, 1f)
fadeIn.duration = 300 // animations pog fadeIn.duration = 300 // animations pog
gridView.layoutAnimation = LayoutAnimationController(fadeIn) gridView.layoutAnimation = LayoutAnimationController(fadeIn)
adapter = OfflineAnimeAdapter(requireContext(), downloads, this) adapter = OfflineAnimeAdapter(requireContext(), downloads, this)
getDownloads()
gridView.adapter = adapter gridView.adapter = adapter
gridView.scheduleLayoutAnimation() gridView.scheduleLayoutAnimation()
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List" total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
@@ -169,20 +175,22 @@ 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 } downloadManager.animeDownloadedTypes.firstOrNull { it.title == item.title.findValidName() }
media?.let { media?.let {
val mediaModel = getMedia(it) lifecycleScope.launch {
if (mediaModel == null) { val mediaModel = getMedia(it)
snackString("Error loading media.json") if (mediaModel == null) {
return@let snackString("Error loading media.json")
return@launch
}
MediaDetailsActivity.mediaSingleton = mediaModel
ContextCompat.startActivity(
requireActivity(),
Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("download", true),
null
)
} }
MediaDetailsActivity.mediaSingleton = mediaModel
ContextCompat.startActivity(
requireActivity(),
Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("download", true),
null
)
} ?: run { } ?: run {
snackString("no media found") snackString("no media found")
} }
@@ -206,8 +214,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
snackString("No media found") // if this happens, terrible things have happened snackString("No media found") // if this happens, terrible things have happened
} }
getDownloads() getDownloads()
adapter.setItems(downloads)
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
} }
builder.setNegativeButton("No") { _, _ -> builder.setNegativeButton("No") { _, _ ->
// Do nothing // Do nothing
@@ -235,7 +241,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
gridView.setOnScrollListener(object : AbsListView.OnScrollListener { gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) { override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
// Implement behavior for different scroll states if needed
} }
override fun onScroll( override fun onScroll(
@@ -258,7 +263,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
getDownloads() getDownloads()
adapter.notifyDataSetChanged()
} }
override fun onPause() { override fun onPause() {
@@ -278,20 +282,34 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
private fun getDownloads() { private fun getDownloads() {
downloads = listOf() downloads = listOf()
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct() if (downloadsJob.isActive) {
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>() downloadsJob.cancel()
for (title in animeTitles) { }
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title } downloadsJob = Job()
val download = tDownloads.first() CoroutineScope(Dispatchers.IO + downloadsJob).launch {
val offlineAnimeModel = loadOfflineAnimeModel(download) val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
newAnimeDownloads += offlineAnimeModel val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
for (title in animeTitles) {
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineAnimeModel = loadOfflineAnimeModel(download)
newAnimeDownloads += offlineAnimeModel
}
downloads = newAnimeDownloads
withContext(Dispatchers.Main) {
adapter.setItems(downloads)
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
adapter.notifyDataSetChanged()
}
} }
downloads = newAnimeDownloads
} }
private fun getMedia(downloadedType: DownloadedType): Media? { /**
val type = downloadedType.type.asText() * Load media.json file from the directory and convert it to Media class
//load media.json and convert to media class with gson * @param downloadedType DownloadedType object
* @return Media object
*/
private suspend fun getMedia(downloadedType: DownloadedType): Media? {
return try { return try {
val directory = DownloadsManager.getSubDirectory( val directory = DownloadsManager.getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,
@@ -310,10 +328,11 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
.create() .create()
val media = directory?.findFile("media.json") val media = directory?.findFile("media.json")
?: return null ?: return null
val mediaJson = media.openInputStream(context?:currContext()!!)?.bufferedReader().use { val mediaJson =
it?.readText() media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
} it?.readText()
?: return null }
?: return null
gson.fromJson(mediaJson, Media::class.java) gson.fromJson(mediaJson, Media::class.java)
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}") Logger.log("Error loading media.json: ${e.message}")
@@ -323,9 +342,13 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} }
} }
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel { /**
* Load OfflineAnimeModel from the directory
* @param downloadedType DownloadedType object
* @return OfflineAnimeModel object
*/
private suspend fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
val type = downloadedType.type.asText() val type = downloadedType.type.asText()
//load media.json and convert to media class with gson
try { try {
val directory = DownloadsManager.getSubDirectory( val directory = DownloadsManager.getSubDirectory(
context ?: currContext()!!, downloadedType.type, context ?: currContext()!!, downloadedType.type,

View File

@@ -3,7 +3,6 @@ package ani.dantotsu.download.manga
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.TypedValue import android.util.TypedValue
@@ -23,6 +22,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.bottomBar import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
@@ -50,9 +50,13 @@ import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SChapterImpl import eu.kanade.tachiyomi.source.model.SChapterImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
@@ -61,6 +65,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private lateinit var gridView: GridView private lateinit var gridView: GridView
private lateinit var adapter: OfflineMangaAdapter private lateinit var adapter: OfflineMangaAdapter
private lateinit var total: TextView private lateinit var total: TextView
private var downloadsJob: Job = Job()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@@ -150,11 +155,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private fun grid() { private fun grid() {
gridView.visibility = View.VISIBLE gridView.visibility = View.VISIBLE
getDownloads()
val fadeIn = AlphaAnimation(0f, 1f) val fadeIn = AlphaAnimation(0f, 1f)
fadeIn.duration = 300 // animations pog fadeIn.duration = 300 // animations pog
gridView.layoutAnimation = LayoutAnimationController(fadeIn) gridView.layoutAnimation = LayoutAnimationController(fadeIn)
adapter = OfflineMangaAdapter(requireContext(), downloads, this) adapter = OfflineMangaAdapter(requireContext(), downloads, this)
getDownloads()
gridView.adapter = adapter gridView.adapter = adapter
gridView.scheduleLayoutAnimation() gridView.scheduleLayoutAnimation()
total.text = total.text =
@@ -166,14 +171,15 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title } downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
media?.let { media?.let {
lifecycleScope.launch {
ContextCompat.startActivity( ContextCompat.startActivity(
requireActivity(), requireActivity(),
Intent(requireContext(), MediaDetailsActivity::class.java) Intent(requireContext(), MediaDetailsActivity::class.java)
.putExtra("media", getMedia(it)) .putExtra("media", getMedia(it))
.putExtra("download", true), .putExtra("download", true),
null null
) )
}
} ?: run { } ?: run {
snackString("no media found") snackString("no media found")
} }
@@ -196,9 +202,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
builder.setPositiveButton("Yes") { _, _ -> builder.setPositiveButton("Yes") { _, _ ->
downloadManager.removeMedia(item.title, type) downloadManager.removeMedia(item.title, type)
getDownloads() getDownloads()
adapter.setItems(downloads)
total.text =
if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
} }
builder.setNegativeButton("No") { _, _ -> builder.setNegativeButton("No") { _, _ ->
// Do nothing // Do nothing
@@ -227,7 +230,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
gridView.setOnScrollListener(object : AbsListView.OnScrollListener { gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) { override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
// Implement behavior for different scroll states if needed
} }
override fun onScroll( override fun onScroll(
@@ -250,7 +252,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
getDownloads() getDownloads()
adapter.notifyDataSetChanged()
} }
override fun onPause() { override fun onPause() {
@@ -270,33 +271,51 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
private fun getDownloads() { private fun getDownloads() {
downloads = listOf() downloads = listOf()
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() if (downloadsJob.isActive) {
val newMangaDownloads = mutableListOf<OfflineMangaModel>() downloadsJob.cancel()
for (title in mangaTitles) {
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel
} }
downloads = newMangaDownloads downloads = listOf()
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() downloadsJob = Job()
val newNovelDownloads = mutableListOf<OfflineMangaModel>() CoroutineScope(Dispatchers.IO + downloadsJob).launch {
for (title in novelTitles) { val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title } val newMangaDownloads = mutableListOf<OfflineMangaModel>()
val download = tDownloads.first() for (title in mangaTitles) {
val offlineMangaModel = loadOfflineMangaModel(download) val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
newNovelDownloads += offlineMangaModel val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newMangaDownloads += offlineMangaModel
}
downloads = newMangaDownloads
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
for (title in novelTitles) {
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
val download = tDownloads.first()
val offlineMangaModel = loadOfflineMangaModel(download)
newNovelDownloads += offlineMangaModel
}
downloads += newNovelDownloads
withContext(Dispatchers.Main) {
adapter.setItems(downloads)
total.text =
if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
adapter.notifyDataSetChanged()
}
} }
downloads += newNovelDownloads
} }
private fun getMedia(downloadedType: DownloadedType): Media? { /**
val type = downloadedType.type.asText() * Load media.json file from the directory and convert it to Media class
//load media.json and convert to media class with gson * @param downloadedType DownloadedType object
* @return Media object
*/
private suspend fun getMedia(downloadedType: DownloadedType): Media? {
return try { return try {
val directory = getSubDirectory(context?:currContext()!!, downloadedType.type, val directory = getSubDirectory(
false, downloadedType.title) context ?: currContext()!!, downloadedType.type,
false, downloadedType.title
)
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
SChapterImpl() // Provide an instance of SChapterImpl SChapterImpl() // Provide an instance of SChapterImpl
@@ -304,9 +323,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
.create() .create()
val media = directory?.findFile("media.json") val media = directory?.findFile("media.json")
?: return null ?: return null
val mediaJson = media.openInputStream(context?:currContext()!!)?.bufferedReader().use { val mediaJson =
it?.readText() media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
} it?.readText()
}
gson.fromJson(mediaJson, Media::class.java) gson.fromJson(mediaJson, Media::class.java)
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Error loading media.json: ${e.message}") Logger.log("Error loading media.json: ${e.message}")
@@ -316,12 +336,14 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
} }
} }
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
val type = downloadedType.type.asText() val type = downloadedType.type.asText()
//load media.json and convert to media class with gson //load media.json and convert to media class with gson
try { try {
val directory = getSubDirectory(context?:currContext()!!, downloadedType.type, val directory = getSubDirectory(
false, downloadedType.title) context ?: currContext()!!, downloadedType.type,
false, downloadedType.title
)
val mediaModel = getMedia(downloadedType)!! val mediaModel = getMedia(downloadedType)!!
val cover = directory?.findFile("cover.jpg") val cover = directory?.findFile("cover.jpg")
val coverUri: Uri? = if (cover?.exists() == true) { val coverUri: Uri? = if (cover?.exists() == true) {

View File

@@ -16,15 +16,19 @@ import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
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.getSubDirectory
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.media.novel.NovelReadFragment import ani.dantotsu.media.novel.NovelReadFragment
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.anggrayudi.storage.file.forceDelete
import com.anggrayudi.storage.file.openOutputStream
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
@@ -250,24 +254,25 @@ class NovelDownloaderService : Service() {
if (!response.isSuccessful) { if (!response.isSuccessful) {
throw IOException("Failed to download file: ${response.message}") throw IOException("Failed to download file: ${response.message}")
} }
val directory = getSubDirectory(
this@NovelDownloaderService,
MediaType.NOVEL,
false,
task.title,
task.chapter
) ?: throw Exception("Directory not found")
directory.findFile("0.epub")?.forceDelete(this@NovelDownloaderService)
val file = File( val file = directory.createFile("application/epub+zip", "0.epub")
this@NovelDownloaderService.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), ?: throw Exception("File not created")
"Dantotsu/Novel/${task.title}/${task.chapter}/0.epub"
)
// Create directories if they don't exist
file.parentFile?.takeIf { !it.exists() }?.mkdirs()
// Overwrite existing file
if (file.exists()) file.delete()
//download cover //download cover
task.coverUrl?.let { task.coverUrl?.let {
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") } file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
} }
val outputStream = this@NovelDownloaderService.contentResolver.openOutputStream(file.uri) ?: throw Exception("Could not open OutputStream")
val sink = file.sink().buffer() val sink = outputStream.sink().buffer()
val responseBody = response.body val responseBody = response.body
val totalBytes = responseBody.contentLength() val totalBytes = responseBody.contentLength()
var downloadedBytes = 0L var downloadedBytes = 0L
@@ -352,13 +357,16 @@ class NovelDownloaderService : Service() {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
private fun saveMediaInfo(task: DownloadTask) { private fun saveMediaInfo(task: DownloadTask) {
launchIO { launchIO {
val directory = File( val directory =
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), DownloadsManager.getSubDirectory(
"Dantotsu/Novel/${task.title}" this@NovelDownloaderService,
) MediaType.NOVEL,
if (!directory.exists()) directory.mkdirs() false,
task.title
val file = File(directory, "media.json") ) ?: throw Exception("Directory not found")
directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService)
val file = directory.createFile("application/json", "media.json")
?: throw Exception("File not created")
val gson = GsonBuilder() val gson = GsonBuilder()
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> { .registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
SChapterImpl() // Provide an instance of SChapterImpl SChapterImpl() // Provide an instance of SChapterImpl
@@ -372,33 +380,47 @@ class NovelDownloaderService : Service() {
val jsonString = gson.toJson(media) val jsonString = gson.toJson(media)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
file.writeText(jsonString) try {
file.openOutputStream(this@NovelDownloaderService, false).use { output ->
if (output == null) throw Exception("Output stream is null")
output.write(jsonString.toByteArray())
}
} catch (e: android.system.ErrnoException) {
e.printStackTrace()
Toast.makeText(
this@NovelDownloaderService,
"Error while saving: ${e.localizedMessage}",
Toast.LENGTH_LONG
).show()
}
} }
} }
} }
} }
private suspend fun downloadImage(url: String, directory: File, name: String): String? = private suspend fun downloadImage(url: String, directory: DocumentFile, name: String): String? =
withContext( withContext(
Dispatchers.IO Dispatchers.IO
) { ) {
var connection: HttpURLConnection? = null var connection: HttpURLConnection? = null
println("Downloading url $url") Logger.log("Downloading url $url")
try { try {
connection = URL(url).openConnection() as HttpURLConnection connection = URL(url).openConnection() as HttpURLConnection
connection.connect() connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) { if (connection.responseCode != HttpURLConnection.HTTP_OK) {
throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}") throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
} }
directory.findFile(name)?.forceDelete(this@NovelDownloaderService)
val file = File(directory, name) val file =
FileOutputStream(file).use { output -> directory.createFile("image/jpeg", name) ?: throw Exception("File not created")
file.openOutputStream(this@NovelDownloaderService, false).use { output ->
if (output == null) throw Exception("Output stream is null")
connection.inputStream.use { input -> connection.inputStream.use { input ->
input.copyTo(output) input.copyTo(output)
} }
} }
return@withContext file.absolutePath return@withContext file.uri.toString()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.currContext
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
@@ -94,23 +95,23 @@ class NovelReadFragment : Fragment(),
) )
) )
) { ) {
val file = File( try {
context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), val directory =
"a/${media.mainName()}/${novel.name}/0.epub" //FIXME DownloadsManager.getSubDirectory(context?:currContext()!!, MediaType.NOVEL, false, novel.name)
) val file = directory?.findFile(novel.name)
if (!file.exists()) return false if (file?.exists() == false) return false
val fileUri = FileProvider.getUriForFile( val fileUri = file?.uri ?: return false
requireContext(), val intent = Intent(context, NovelReaderActivity::class.java).apply {
"${requireContext().packageName}.provider", action = Intent.ACTION_VIEW
file setDataAndType(fileUri, "application/epub+zip")
) flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
val intent = Intent(context, NovelReaderActivity::class.java).apply { }
action = Intent.ACTION_VIEW startActivity(intent)
setDataAndType(fileUri, "application/epub+zip") return true
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION } catch (e: Exception) {
Logger.log(e)
return false
} }
startActivity(intent)
return true
} else { } else {
return false return false
} }

View File

@@ -1,9 +1,12 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.app.Application
import android.os.Environment 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.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaType
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
@@ -11,6 +14,7 @@ import java.io.File
class OfflineNovelParser : NovelParser() { class OfflineNovelParser : NovelParser() {
private val downloadManager = Injekt.get<DownloadsManager>() private val downloadManager = Injekt.get<DownloadsManager>()
private val context = Injekt.get<Application>()
override val hostUrl: String = "Offline" override val hostUrl: String = "Offline"
override val name: String = "Offline" override val name: String = "Offline"
@@ -21,19 +25,16 @@ class OfflineNovelParser : NovelParser() {
override suspend fun loadBook(link: String, extra: Map<String, String>?): Book { override suspend fun loadBook(link: String, extra: Map<String, String>?): Book {
//link should be a directory //link should be a directory
val directory = File( val directory = getSubDirectory(context, MediaType.NOVEL, false, link)
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Novel/$link"
)
val chapters = mutableListOf<Book>() val chapters = mutableListOf<Book>()
if (directory.exists()) { if (directory?.exists() == true) {
directory.listFiles()?.forEach { directory.listFiles().forEach {
if (it.isDirectory) { if (it.isDirectory) {
val chapter = Book( val chapter = Book(
it.name, it.name?:"Unknown",
it.absolutePath + "/cover.jpg", it.uri.toString(),
null, null,
listOf(it.absolutePath + "/0.epub") listOf(it.uri.toString())
) )
chapters.add(chapter) chapters.add(chapter)
} }
@@ -60,20 +61,16 @@ class OfflineNovelParser : NovelParser() {
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
val directory = File( val directory = getSubDirectory(context, MediaType.NOVEL, false, title)
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Novel/$title"
)
val names = mutableListOf<String>() val names = mutableListOf<String>()
if (directory.exists()) { if (directory?.exists() == true) {
directory.listFiles()?.forEach { directory.listFiles().forEach {
if (it.isDirectory) { if (it.isDirectory) {
names.add(it.name) names.add(it.name?: "Unknown")
} }
} }
} }
val cover = val cover = directory?.findFile("cover.jpg")?.uri.toString()
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + "/Dantotsu/Novel/$title/cover.jpg"
names.forEach { names.forEach {
returnList.add(ShowResponse(it, it, cover)) returnList.add(ShowResponse(it, it, cover))
} }