offline novel
This commit is contained in:
parent
111fb16266
commit
3ded6ba87a
18 changed files with 512 additions and 212 deletions
|
@ -48,6 +48,32 @@ class DownloadsManager(private val context: Context) {
|
|||
saveDownloads()
|
||||
}
|
||||
|
||||
fun removeMedia(title: String, type: Download.Type) {
|
||||
val subDirectory = if (type == Download.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (type == Download.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val directory = File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$subDirectory/$title"
|
||||
)
|
||||
if (directory.exists()) {
|
||||
val deleted = directory.deleteRecursively()
|
||||
if (deleted) {
|
||||
Toast.makeText(context, "Successfully deleted", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to delete directory", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
downloadsList.removeAll { it.title == title }
|
||||
saveDownloads()
|
||||
}
|
||||
|
||||
fun queryDownload(download: Download): Boolean {
|
||||
return downloadsList.contains(download)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.core.content.ContextCompat
|
|||
import ani.dantotsu.R
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.manga.ImageData
|
||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
||||
|
@ -175,74 +176,90 @@ class MangaDownloaderService : Service() {
|
|||
}
|
||||
|
||||
suspend fun download(task: DownloadTask) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
this@MangaDownloaderService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
val deferredList = mutableListOf<Deferred<Bitmap?>>()
|
||||
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
// Loop through each ImageData object from the task
|
||||
var farthest = 0
|
||||
for ((index, image) in task.imageData.withIndex()) {
|
||||
// Limit the number of simultaneous downloads from the task
|
||||
if (deferredList.size >= task.simultaneousDownloads) {
|
||||
// Wait for all deferred to complete and clear the list
|
||||
deferredList.awaitAll()
|
||||
deferredList.clear()
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
this@MangaDownloaderService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
// Download the image and add to deferred list
|
||||
val deferred = async(Dispatchers.IO) {
|
||||
var bitmap: Bitmap? = null
|
||||
var retryCount = 0
|
||||
val deferredList = mutableListOf<Deferred<Bitmap?>>()
|
||||
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
while (bitmap == null && retryCount < task.retries) {
|
||||
bitmap = image.fetchAndProcessImage(
|
||||
image.page,
|
||||
image.source,
|
||||
this@MangaDownloaderService
|
||||
// Loop through each ImageData object from the task
|
||||
var farthest = 0
|
||||
for ((index, image) in task.imageData.withIndex()) {
|
||||
// Limit the number of simultaneous downloads from the task
|
||||
if (deferredList.size >= task.simultaneousDownloads) {
|
||||
// Wait for all deferred to complete and clear the list
|
||||
deferredList.awaitAll()
|
||||
deferredList.clear()
|
||||
}
|
||||
|
||||
// Download the image and add to deferred list
|
||||
val deferred = async(Dispatchers.IO) {
|
||||
var bitmap: Bitmap? = null
|
||||
var retryCount = 0
|
||||
|
||||
while (bitmap == null && retryCount < task.retries) {
|
||||
bitmap = image.fetchAndProcessImage(
|
||||
image.page,
|
||||
image.source,
|
||||
this@MangaDownloaderService
|
||||
)
|
||||
retryCount++
|
||||
}
|
||||
|
||||
// Cache the image if successful
|
||||
if (bitmap != null) {
|
||||
saveToDisk("$index.jpg", bitmap, task.title, task.chapter)
|
||||
}
|
||||
farthest++
|
||||
builder.setProgress(task.imageData.size, farthest, false)
|
||||
broadcastDownloadProgress(
|
||||
task.chapter,
|
||||
farthest * 100 / task.imageData.size
|
||||
)
|
||||
retryCount++
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
bitmap
|
||||
}
|
||||
|
||||
// Cache the image if successful
|
||||
if (bitmap != null) {
|
||||
saveToDisk("$index.jpg", bitmap, task.title, task.chapter)
|
||||
}
|
||||
farthest++
|
||||
builder.setProgress(task.imageData.size, farthest, false)
|
||||
broadcastDownloadProgress(task.chapter, farthest * 100 / task.imageData.size)
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
bitmap
|
||||
deferredList.add(deferred)
|
||||
}
|
||||
|
||||
deferredList.add(deferred)
|
||||
// Wait for any remaining deferred to complete
|
||||
deferredList.awaitAll()
|
||||
|
||||
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||
.setProgress(0, 0, false)
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(
|
||||
Download(
|
||||
task.title,
|
||||
task.chapter,
|
||||
Download.Type.MANGA
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.chapter)
|
||||
snackString("${task.title} - ${task.chapter} Download finished")
|
||||
}
|
||||
|
||||
// Wait for any remaining deferred to complete
|
||||
deferredList.awaitAll()
|
||||
|
||||
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||
.setProgress(0, 0, false)
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(Download(task.title, task.chapter, Download.Type.MANGA))
|
||||
broadcastDownloadFinished(task.chapter)
|
||||
snackString("${task.title} - ${task.chapter} Download finished")
|
||||
} catch (e: Exception) {
|
||||
logger("Exception while downloading file: ${e.message}")
|
||||
snackString("Exception while downloading file: ${e.message}")
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
broadcastDownloadFailed(task.chapter)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,12 @@ import ani.dantotsu.R
|
|||
|
||||
class OfflineMangaAdapter(
|
||||
private val context: Context,
|
||||
private val items: List<OfflineMangaModel>
|
||||
private var items: List<OfflineMangaModel>,
|
||||
private val searchListener: OfflineMangaSearchListener
|
||||
) : BaseAdapter() {
|
||||
private val inflater: LayoutInflater =
|
||||
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
private var originalItems: List<OfflineMangaModel> = items
|
||||
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
|
@ -54,4 +56,22 @@ class OfflineMangaAdapter(
|
|||
}
|
||||
return view
|
||||
}
|
||||
|
||||
fun onSearchQuery(query: String) {
|
||||
// Implement the filtering logic here, for example:
|
||||
items = if (query.isEmpty()) {
|
||||
// Return the original list if the query is empty
|
||||
originalItems
|
||||
} else {
|
||||
// Filter the list based on the query
|
||||
originalItems.filter { it.title.contains(query, ignoreCase = true) }
|
||||
}
|
||||
notifyDataSetChanged() // Notify the adapter that the data set has changed
|
||||
}
|
||||
|
||||
fun setItems(items: List<OfflineMangaModel>) {
|
||||
this.items = items
|
||||
this.originalItems = items
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -7,11 +7,14 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.GridView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
|
@ -41,7 +44,7 @@ import java.io.File
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class OfflineMangaFragment : Fragment() {
|
||||
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||
private var downloads: List<OfflineMangaModel> = listOf()
|
||||
private lateinit var gridView: GridView
|
||||
|
@ -79,15 +82,29 @@ class OfflineMangaFragment : Fragment() {
|
|||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||
}
|
||||
|
||||
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
||||
searchView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
onSearchQuery(s.toString())
|
||||
}
|
||||
})
|
||||
|
||||
gridView = view.findViewById(R.id.gridView)
|
||||
getDownloads()
|
||||
adapter = OfflineMangaAdapter(requireContext(), downloads)
|
||||
adapter = OfflineMangaAdapter(requireContext(), downloads, this)
|
||||
gridView.adapter = adapter
|
||||
gridView.setOnItemClickListener { parent, view, position, id ->
|
||||
// Get the OfflineMangaModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineMangaModel
|
||||
val media =
|
||||
downloadManager.mangaDownloads.filter { it.title == item.title }.firstOrNull()
|
||||
downloadManager.mangaDownloads.firstOrNull { it.title == item.title }
|
||||
?: downloadManager.novelDownloads.firstOrNull { it.title == item.title }
|
||||
media?.let {
|
||||
startActivity(
|
||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||
|
@ -99,9 +116,37 @@ class OfflineMangaFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
Download.Type.NOVEL
|
||||
}
|
||||
// Alert dialog to confirm deletion
|
||||
val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.DialogTheme)
|
||||
builder.setTitle("Delete ${item.title}?")
|
||||
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
||||
builder.setPositiveButton("Yes") { _, _ ->
|
||||
downloadManager.removeMedia(item.title, type)
|
||||
getDownloads()
|
||||
adapter.setItems(downloads)
|
||||
}
|
||||
builder.setNegativeButton("No") { _, _ ->
|
||||
// Do nothing
|
||||
}
|
||||
builder.show()
|
||||
true
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onSearchQuery(query: String) {
|
||||
adapter.onSearchQuery(query)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
var height = statusBarHeight
|
||||
|
@ -164,24 +209,42 @@ class OfflineMangaFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun getDownloads() {
|
||||
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||
val newDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in titles) {
|
||||
downloads = listOf()
|
||||
val mangaTitles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in mangaTitles) {
|
||||
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||
newDownloads += offlineMangaModel
|
||||
newMangaDownloads += offlineMangaModel
|
||||
}
|
||||
downloads = newDownloads
|
||||
downloads = newMangaDownloads
|
||||
val novelTitles = downloadManager.novelDownloads.map { it.title }.distinct()
|
||||
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in novelTitles) {
|
||||
val _downloads = downloadManager.novelDownloads.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||
newNovelDownloads += offlineMangaModel
|
||||
}
|
||||
downloads += newNovelDownloads
|
||||
|
||||
}
|
||||
|
||||
private fun getMedia(download: Download): Media? {
|
||||
val type = if (download.type == Download.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}"
|
||||
"Dantotsu/$type/${download.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
try {
|
||||
return try {
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||
SChapterImpl() // Provide an instance of SChapterImpl
|
||||
|
@ -189,19 +252,26 @@ class OfflineMangaFragment : Fragment() {
|
|||
.create()
|
||||
val media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
return gson.fromJson(mediaJson, Media::class.java)
|
||||
gson.fromJson(mediaJson, Media::class.java)
|
||||
} catch (e: Exception) {
|
||||
logger("Error loading media.json: ${e.message}")
|
||||
logger(e.printStackTrace())
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
return null
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel {
|
||||
val type = if (download.type == Download.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}"
|
||||
"Dantotsu/$type/${download.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
try {
|
||||
|
@ -215,8 +285,8 @@ class OfflineMangaFragment : Fragment() {
|
|||
null
|
||||
}
|
||||
val title = mediaModel.nameMAL ?: "unknown"
|
||||
val score = if (mediaModel.userScore != 0) mediaModel.userScore.toString() else
|
||||
if (mediaModel.meanScore == null) "?" else mediaModel.meanScore.toString()
|
||||
val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore
|
||||
?: 0) else mediaModel.userScore) / 10.0).toString()
|
||||
val isOngoing = false
|
||||
val isUserScored = mediaModel.userScore != 0
|
||||
return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
|
||||
|
@ -227,4 +297,8 @@ class OfflineMangaFragment : Fragment() {
|
|||
return OfflineMangaModel("unknown", "0", false, false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OfflineMangaSearchListener {
|
||||
fun onSearchQuery(query: String)
|
||||
}
|
|
@ -174,7 +174,7 @@ class NovelDownloaderService : Service() {
|
|||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
suspend fun isEpubFile(urlString: String): Boolean {
|
||||
private suspend fun isEpubFile(urlString: String): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
|
@ -200,122 +200,150 @@ class NovelDownloaderService : Service() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun isAlreadyDownloaded(urlString: String): Boolean {
|
||||
return urlString.contains("file://")
|
||||
}
|
||||
|
||||
suspend fun download(task: DownloadTask) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
this@NovelDownloaderService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
this@NovelDownloaderService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
broadcastDownloadStarted(task.originalLink)
|
||||
broadcastDownloadStarted(task.originalLink)
|
||||
|
||||
if (notifi) {
|
||||
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
if (notifi) {
|
||||
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
if (!isEpubFile(task.downloadLink)) {
|
||||
logger("Download link is not an .epub file")
|
||||
broadcastDownloadFailed(task.originalLink)
|
||||
snackString("Download link is not an .epub file")
|
||||
return@withContext
|
||||
}
|
||||
if (!isEpubFile(task.downloadLink)) {
|
||||
if (isAlreadyDownloaded(task.originalLink)) {
|
||||
logger("Already downloaded")
|
||||
broadcastDownloadFinished(task.originalLink)
|
||||
snackString("Already downloaded")
|
||||
return@withContext
|
||||
}
|
||||
logger("Download link is not an .epub file")
|
||||
broadcastDownloadFailed(task.originalLink)
|
||||
snackString("Download link is not an .epub file")
|
||||
return@withContext
|
||||
}
|
||||
|
||||
// Start the download
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
.url(task.downloadLink)
|
||||
.build()
|
||||
// Start the download
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
.url(task.downloadLink)
|
||||
.build()
|
||||
|
||||
networkHelper.downloadClient.newCall(request).execute().use { response ->
|
||||
// Ensure the response is successful and has a body
|
||||
if (!response.isSuccessful || response.body == null) {
|
||||
throw IOException("Failed to download file: ${response.message}")
|
||||
}
|
||||
networkHelper.downloadClient.newCall(request).execute().use { response ->
|
||||
// Ensure the response is successful and has a body
|
||||
if (!response.isSuccessful || response.body == null) {
|
||||
throw IOException("Failed to download file: ${response.message}")
|
||||
}
|
||||
|
||||
val file = File(
|
||||
this@NovelDownloaderService.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${task.title}/${task.chapter}/0.epub"
|
||||
)
|
||||
val file = File(
|
||||
this@NovelDownloaderService.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${task.title}/${task.chapter}/0.epub"
|
||||
)
|
||||
|
||||
// Create directories if they don't exist
|
||||
file.parentFile?.takeIf { !it.exists() }?.mkdirs()
|
||||
// Create directories if they don't exist
|
||||
file.parentFile?.takeIf { !it.exists() }?.mkdirs()
|
||||
|
||||
// Overwrite existing file
|
||||
if (file.exists()) file.delete()
|
||||
// Overwrite existing file
|
||||
if (file.exists()) file.delete()
|
||||
|
||||
//download cover
|
||||
task.coverUrl?.let {
|
||||
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
|
||||
}
|
||||
//download cover
|
||||
task.coverUrl?.let {
|
||||
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
|
||||
}
|
||||
|
||||
val sink = file.sink().buffer()
|
||||
val responseBody = response.body
|
||||
val totalBytes = responseBody.contentLength()
|
||||
var downloadedBytes = 0L
|
||||
val sink = file.sink().buffer()
|
||||
val responseBody = response.body
|
||||
val totalBytes = responseBody.contentLength()
|
||||
var downloadedBytes = 0L
|
||||
|
||||
val notificationUpdateInterval = 1024 * 1024 // 1 MB
|
||||
val broadcastUpdateInterval = 1024 * 256 // 256 KB
|
||||
var lastNotificationUpdate = 0L
|
||||
var lastBroadcastUpdate = 0L
|
||||
val notificationUpdateInterval = 1024 * 1024 // 1 MB
|
||||
val broadcastUpdateInterval = 1024 * 256 // 256 KB
|
||||
var lastNotificationUpdate = 0L
|
||||
var lastBroadcastUpdate = 0L
|
||||
|
||||
responseBody.source().use { source ->
|
||||
while (true) {
|
||||
val read = source.read(sink.buffer, 8192)
|
||||
if (read == -1L) break
|
||||
downloadedBytes += read
|
||||
sink.emit()
|
||||
responseBody.source().use { source ->
|
||||
while (true) {
|
||||
val read = source.read(sink.buffer, 8192)
|
||||
if (read == -1L) break
|
||||
downloadedBytes += read
|
||||
sink.emit()
|
||||
|
||||
// Update progress at intervals
|
||||
if (downloadedBytes - lastNotificationUpdate >= notificationUpdateInterval) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val progress = (downloadedBytes * 100 / totalBytes).toInt()
|
||||
builder.setProgress(100, progress, false)
|
||||
if (notifi) {
|
||||
notificationManager.notify(
|
||||
NOTIFICATION_ID,
|
||||
builder.build()
|
||||
)
|
||||
// Update progress at intervals
|
||||
if (downloadedBytes - lastNotificationUpdate >= notificationUpdateInterval) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val progress =
|
||||
(downloadedBytes * 100 / totalBytes).toInt()
|
||||
builder.setProgress(100, progress, false)
|
||||
if (notifi) {
|
||||
notificationManager.notify(
|
||||
NOTIFICATION_ID,
|
||||
builder.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
lastNotificationUpdate = downloadedBytes
|
||||
}
|
||||
lastNotificationUpdate = downloadedBytes
|
||||
}
|
||||
if (downloadedBytes - lastBroadcastUpdate >= broadcastUpdateInterval) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val progress = (downloadedBytes * 100 / totalBytes).toInt()
|
||||
logger("Download progress: $progress")
|
||||
broadcastDownloadProgress(task.originalLink, progress)
|
||||
if (downloadedBytes - lastBroadcastUpdate >= broadcastUpdateInterval) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val progress =
|
||||
(downloadedBytes * 100 / totalBytes).toInt()
|
||||
logger("Download progress: $progress")
|
||||
broadcastDownloadProgress(task.originalLink, progress)
|
||||
}
|
||||
lastBroadcastUpdate = downloadedBytes
|
||||
}
|
||||
lastBroadcastUpdate = downloadedBytes
|
||||
}
|
||||
}
|
||||
|
||||
sink.close()
|
||||
//if the file is smaller than 95% of totalBytes, it means the download was interrupted
|
||||
if (file.length() < totalBytes * 0.95) {
|
||||
throw IOException("Failed to download file: ${response.message}")
|
||||
}
|
||||
}
|
||||
|
||||
sink.close()
|
||||
} catch (e: Exception) {
|
||||
logger("Exception while downloading .epub inside request: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger("Exception while downloading .epub: ${e.message}")
|
||||
snackString("Exception while downloading .epub: ${e.message}")
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Update notification for download completion
|
||||
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||
.setProgress(0, 0, false)
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
// Update notification for download completion
|
||||
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||
.setProgress(0, 0, false)
|
||||
if (notifi) {
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(Download(task.title, task.chapter, Download.Type.NOVEL))
|
||||
broadcastDownloadFinished(task.originalLink)
|
||||
snackString("${task.title} - ${task.chapter} Download finished")
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(
|
||||
Download(
|
||||
task.title,
|
||||
task.chapter,
|
||||
Download.Type.NOVEL
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.originalLink)
|
||||
snackString("${task.title} - ${task.chapter} Download finished")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger("Exception while downloading .epub: ${e.message}")
|
||||
snackString("Exception while downloading .epub: ${e.message}")
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
broadcastDownloadFailed(task.originalLink)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue