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