mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-09-09 19:31:51 +02:00
Use actual file size from server it size does not match metadata
This commit is contained in:
@@ -4,13 +4,13 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.file.fullName
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.device.FolderScanner
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
import com.audiobookshelf.app.models.DownloadItem
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
import com.audiobookshelf.app.models.DownloadItemPart
|
import com.audiobookshelf.app.models.DownloadItemPart
|
||||||
import com.audiobookshelf.app.plugins.AbsLogger
|
import com.audiobookshelf.app.plugins.AbsLogger
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import com.anggrayudi.storage.file.fullName
|
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
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
|
||||||
@@ -59,6 +59,7 @@ class DownloadItemManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InternalProgressCallback {
|
interface InternalProgressCallback {
|
||||||
|
fun onSizeResolved(totalBytes: Long)
|
||||||
fun onProgress(totalBytesWritten: Long, progress: Long)
|
fun onProgress(totalBytesWritten: Long, progress: Long)
|
||||||
fun onComplete(failed: Boolean)
|
fun onComplete(failed: Boolean)
|
||||||
fun onAuthError()
|
fun onAuthError()
|
||||||
@@ -263,6 +264,21 @@ class DownloadItemManager(
|
|||||||
stagingFile,
|
stagingFile,
|
||||||
part.fileSize,
|
part.fileSize,
|
||||||
object : InternalProgressCallback {
|
object : InternalProgressCallback {
|
||||||
|
override fun onSizeResolved(totalBytes: Long) {
|
||||||
|
synchronized(this@DownloadItemManager) {
|
||||||
|
if (part !in currentDownloadItemParts || totalBytes < 0L) return
|
||||||
|
if (part.fileSize == totalBytes) return
|
||||||
|
AbsLogger.info(
|
||||||
|
tag,
|
||||||
|
"Using server size $totalBytes instead of metadata size ${part.fileSize} for ${part.filename}"
|
||||||
|
)
|
||||||
|
part.fileSize = totalBytes
|
||||||
|
part.lastUpdateTime = System.currentTimeMillis()
|
||||||
|
persist(item, force = true)
|
||||||
|
clientEventEmitter.onDownloadItemPartUpdate(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onProgress(totalBytesWritten: Long, progress: Long) {
|
override fun onProgress(totalBytesWritten: Long, progress: Long) {
|
||||||
synchronized(this@DownloadItemManager) {
|
synchronized(this@DownloadItemManager) {
|
||||||
if (part !in currentDownloadItemParts) return
|
if (part !in currentDownloadItemParts) return
|
||||||
@@ -395,7 +411,10 @@ class DownloadItemManager(
|
|||||||
if (newAccessToken.isNullOrEmpty()) {
|
if (newAccessToken.isNullOrEmpty()) {
|
||||||
failParkedAuthParts(serverConnectionConfigId)
|
failParkedAuthParts(serverConnectionConfigId)
|
||||||
} else {
|
} else {
|
||||||
AbsLogger.info(tag, "Token refresh succeeded; resuming downloads for $serverConnectionConfigId")
|
AbsLogger.info(
|
||||||
|
tag,
|
||||||
|
"Token refresh succeeded; resuming downloads for $serverConnectionConfigId"
|
||||||
|
)
|
||||||
checkUpdateDownloadQueue()
|
checkUpdateDownloadQueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-21
@@ -64,20 +64,6 @@ class InternalDownloadManager(
|
|||||||
AbsLogger.info(
|
AbsLogger.info(
|
||||||
tag,
|
tag,
|
||||||
"Starting ${if (existingBytes > 0L) "resumed" else "new"} download for ${destinationFile.name} at byte $existingBytes")
|
"Starting ${if (existingBytes > 0L) "resumed" else "new"} download for ${destinationFile.name} at byte $existingBytes")
|
||||||
if (expectedSize > 0L && existingBytes == expectedSize) {
|
|
||||||
progressCallback.onProgress(existingBytes, 100L)
|
|
||||||
AbsLogger.info(tag, "Download completed for ${destinationFile.name} ($existingBytes bytes)")
|
|
||||||
progressCallback.onComplete(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (expectedSize > 0L && existingBytes > expectedSize) {
|
|
||||||
if (!destinationFile.delete()) {
|
|
||||||
AbsLogger.error(tag, "Could not delete oversized staging file ${destinationFile.name}")
|
|
||||||
progressCallback.onComplete(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
existingBytes = 0L
|
|
||||||
}
|
|
||||||
val request =
|
val request =
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
@@ -107,7 +93,8 @@ class InternalDownloadManager(
|
|||||||
response.header("Content-Range")
|
response.header("Content-Range")
|
||||||
?.removePrefix("bytes */")
|
?.removePrefix("bytes */")
|
||||||
?.toLongOrNull()
|
?.toLongOrNull()
|
||||||
if (serverSize != null && serverSize > 0L && existingBytes == serverSize) {
|
if (serverSize != null) progressCallback.onSizeResolved(serverSize)
|
||||||
|
if (serverSize != null && existingBytes == serverSize) {
|
||||||
progressCallback.onProgress(existingBytes, 100L)
|
progressCallback.onProgress(existingBytes, 100L)
|
||||||
AbsLogger.info(tag, "Download completed for ${destinationFile.name} ($existingBytes bytes)")
|
AbsLogger.info(tag, "Download completed for ${destinationFile.name} ($existingBytes bytes)")
|
||||||
progressCallback.onComplete(false)
|
progressCallback.onComplete(false)
|
||||||
@@ -140,9 +127,12 @@ class InternalDownloadManager(
|
|||||||
|
|
||||||
val startingBytes = if (append) existingBytes else 0L
|
val startingBytes = if (append) existingBytes else 0L
|
||||||
val responseLength = response.body!!.contentLength()
|
val responseLength = response.body!!.contentLength()
|
||||||
|
val serverSize =
|
||||||
|
if (append) contentRangeTotal(response)
|
||||||
|
else responseLength.takeIf { it >= 0L }
|
||||||
|
if (serverSize != null) progressCallback.onSizeResolved(serverSize)
|
||||||
val totalLength =
|
val totalLength =
|
||||||
if (expectedSize > 0L) expectedSize
|
serverSize ?: if (expectedSize > 0L) expectedSize else 0L
|
||||||
else if (responseLength >= 0L) startingBytes + responseLength else 0L
|
|
||||||
|
|
||||||
FileOutputStream(destinationFile, append).use { output ->
|
FileOutputStream(destinationFile, append).use { output ->
|
||||||
response.body!!.byteStream().use { input ->
|
response.body!!.byteStream().use { input ->
|
||||||
@@ -162,16 +152,17 @@ class InternalDownloadManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedSize > 0L && destinationFile.length() != expectedSize) {
|
val downloadedSize = destinationFile.length()
|
||||||
|
if (serverSize != null && downloadedSize != serverSize) {
|
||||||
AbsLogger.error(
|
AbsLogger.error(
|
||||||
tag,
|
tag,
|
||||||
"Downloaded size for ${destinationFile.name} was ${destinationFile.length()}, expected $expectedSize"
|
"Downloaded size for ${destinationFile.name} was $downloadedSize, expected server size $serverSize"
|
||||||
)
|
)
|
||||||
progressCallback.onComplete(true)
|
progressCallback.onComplete(true)
|
||||||
} else {
|
} else {
|
||||||
AbsLogger.info(
|
AbsLogger.info(
|
||||||
tag,
|
tag,
|
||||||
"Download completed for ${destinationFile.name} (${destinationFile.length()} bytes)"
|
"Download completed for ${destinationFile.name} ($downloadedSize bytes)"
|
||||||
)
|
)
|
||||||
progressCallback.onComplete(false)
|
progressCallback.onComplete(false)
|
||||||
}
|
}
|
||||||
@@ -192,9 +183,16 @@ class InternalDownloadManager(
|
|||||||
match.groupValues[2].toLongOrNull()?.let { it >= offset } == true
|
match.groupValues[2].toLongOrNull()?.let { it >= offset } == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun contentRangeTotal(response: Response): Long? =
|
||||||
|
CONTENT_RANGE.matchEntire(response.header("Content-Range") ?: "")
|
||||||
|
?.groupValues
|
||||||
|
?.get(3)
|
||||||
|
?.takeUnless { it == "*" }
|
||||||
|
?.toLongOrNull()
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val CHUNK_SIZE = 512 * 1024 // 512 KB
|
const val CHUNK_SIZE = 512 * 1024 // 512 KB
|
||||||
val CONTENT_RANGE = Regex("bytes (\\d+)-(\\d+)/(?:\\d+|\\*)")
|
val CONTENT_RANGE = Regex("bytes (\\d+)-(\\d+)/(\\d+|\\*)")
|
||||||
val client =
|
val client =
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ data class DownloadItemPart(
|
|||||||
val id: String,
|
val id: String,
|
||||||
val downloadItemId: String,
|
val downloadItemId: String,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val fileSize: Long,
|
var fileSize: Long,
|
||||||
@JsonIgnore val destinationPath: String,
|
@JsonIgnore val destinationPath: String,
|
||||||
val finalDestinationPath:String,
|
val finalDestinationPath:String,
|
||||||
val serverPath: String,
|
val serverPath: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user