mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-08 04:48:36 +02:00
Initial internal download continue incomplete download
This commit is contained in:
@@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.getcapacitor.JSObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -113,7 +112,6 @@ class DownloadItemManager(
|
||||
val file = File(downloadItemPart.finalDestinationPath)
|
||||
file.parentFile?.mkdirs()
|
||||
|
||||
val fileOutputStream = FileOutputStream(downloadItemPart.finalDestinationPath)
|
||||
val internalProgressCallback =
|
||||
object : InternalProgressCallback {
|
||||
override fun onProgress(totalBytesWritten: Long, progress: Long) {
|
||||
@@ -131,7 +129,11 @@ class DownloadItemManager(
|
||||
tag,
|
||||
"Start internal download to destination path ${downloadItemPart.finalDestinationPath} from ${downloadItemPart.serverUrl}"
|
||||
)
|
||||
InternalDownloadManager(fileOutputStream, internalProgressCallback)
|
||||
InternalDownloadManager(
|
||||
mainActivity,
|
||||
downloadItemPart.finalDestinationUri,
|
||||
internalProgressCallback
|
||||
)
|
||||
.download(downloadItemPart.serverUrl)
|
||||
downloadItemPart.downloadId = 1
|
||||
currentDownloadItemParts.add(downloadItemPart)
|
||||
|
||||
+80
-39
@@ -1,5 +1,7 @@
|
||||
package com.audiobookshelf.app.managers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import java.io.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -12,14 +14,14 @@ import okhttp3.*
|
||||
* @property progressCallback The callback to report download progress.
|
||||
*/
|
||||
class InternalDownloadManager(
|
||||
private val outputStream: FileOutputStream,
|
||||
private val context: Context,
|
||||
private val fileUri: Uri,
|
||||
private val progressCallback: DownloadItemManager.InternalProgressCallback
|
||||
) : AutoCloseable {
|
||||
|
||||
private val tag = "InternalDownloadManager"
|
||||
private val client: OkHttpClient =
|
||||
OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).build()
|
||||
private val writer = BinaryFileWriter(outputStream, progressCallback)
|
||||
|
||||
/**
|
||||
* Downloads a file from the given URL.
|
||||
@@ -29,69 +31,108 @@ class InternalDownloadManager(
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun download(url: String) {
|
||||
val request: Request = Request.Builder().url(url).build()
|
||||
client.newCall(request)
|
||||
.enqueue(
|
||||
object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e(tag, "Download URL $url FAILED", e)
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
try {
|
||||
val existingSize = getInternalFileSize(fileUri)
|
||||
Log.d(tag, "Existing file size: $existingSize bytes")
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
response.body?.let { responseBody ->
|
||||
val length: Long = response.header("Content-Length")?.toLongOrNull() ?: 0L
|
||||
writer.write(responseBody.byteStream(), length)
|
||||
val request =
|
||||
Request.Builder()
|
||||
.url(url)
|
||||
.apply {
|
||||
if (existingSize > 0) {
|
||||
addHeader("Range", "bytes=$existingSize-") // Resume download
|
||||
}
|
||||
?: run {
|
||||
Log.e(tag, "Response doesn't contain a file")
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.build()
|
||||
|
||||
client.newCall(request)
|
||||
.enqueue(
|
||||
object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e(tag, "Download failed: $url", e)
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (!response.isSuccessful) {
|
||||
Log.e(tag, "Failed to download: ${response.code}")
|
||||
progressCallback.onComplete(true)
|
||||
return
|
||||
}
|
||||
|
||||
response.body?.let { responseBody ->
|
||||
val totalLength =
|
||||
(response.header("Content-Length")?.toLongOrNull()
|
||||
?: 0L) + existingSize
|
||||
val outputStream = getOutputStream(fileUri, existingSize)
|
||||
|
||||
outputStream?.let {
|
||||
BinaryFileWriter(it, progressCallback, existingSize).use { writer ->
|
||||
writer.write(responseBody.byteStream(), totalLength)
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
Log.e(tag, "Failed to open output stream")
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
Log.e(tag, "Response doesn't contain a file")
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Download error", e)
|
||||
progressCallback.onComplete(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the download manager and releases resources.
|
||||
*
|
||||
* @throws Exception If an error occurs during closing.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
writer.close()
|
||||
private fun getInternalFileSize(uri: Uri): Long {
|
||||
val file = File(uri.path!!)
|
||||
return if (file.exists()) file.length() else 0L
|
||||
}
|
||||
|
||||
private fun getOutputStream(uri: Uri, existingSize: Long): OutputStream? {
|
||||
val file = File(uri.path!!)
|
||||
return if (file.exists()) {
|
||||
FileOutputStream(file, true).apply {
|
||||
channel.position(existingSize) // Move to the end of the file to append
|
||||
}
|
||||
} else {
|
||||
FileOutputStream(file) // Create a new file if it doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class) override fun close() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes binary data to an output stream.
|
||||
*
|
||||
* @property outputStream The output stream to write the data to.
|
||||
* @property progressCallback The callback to report write progress.
|
||||
*/
|
||||
class BinaryFileWriter(
|
||||
private val outputStream: OutputStream,
|
||||
private val progressCallback: DownloadItemManager.InternalProgressCallback
|
||||
private val progressCallback: DownloadItemManager.InternalProgressCallback,
|
||||
private val existingSize: Long
|
||||
) : AutoCloseable {
|
||||
|
||||
/**
|
||||
* Writes data from the input stream to the output stream.
|
||||
*
|
||||
* @param inputStream The input stream to read the data from.
|
||||
* @param length The total length of the data to be written.
|
||||
* @param totalLength The total length of the data to be written.
|
||||
* @return The total number of bytes written.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun write(inputStream: InputStream, length: Long): Long {
|
||||
fun write(inputStream: InputStream, totalLength: Long): Long {
|
||||
BufferedInputStream(inputStream).use { input ->
|
||||
val dataBuffer = ByteArray(CHUNK_SIZE)
|
||||
var totalBytes: Long = 0
|
||||
var totalBytes = existingSize
|
||||
var readBytes: Int
|
||||
|
||||
while (input.read(dataBuffer).also { readBytes = it } != -1) {
|
||||
totalBytes += readBytes
|
||||
outputStream.write(dataBuffer, 0, readBytes)
|
||||
progressCallback.onProgress(totalBytes, (totalBytes * 100L) / length)
|
||||
progressCallback.onProgress(totalBytes, (totalBytes * 100L) / totalLength)
|
||||
}
|
||||
progressCallback.onComplete(false)
|
||||
return totalBytes
|
||||
|
||||
Reference in New Issue
Block a user