handle a situation when some webservers does not respect range header at first place

This commit is contained in:
AmirHossein Abdolmotallebi 2024-07-10 11:31:28 +03:30
parent c06da4aaa7
commit 5f4ab5889c
7 changed files with 79 additions and 36 deletions

View File

@ -356,9 +356,14 @@ class DownloadJob(
}
private fun onPartHaveToManyError(throwable: Throwable) {
var paused = false
if (throwable is DownloadValidationException) {
scope.launch {
pause(throwable)
if (throwable.isCritical()){
//stop the whole job! as we have big problem here
paused = true
scope.launch {
pause(throwable)
}
}
}
val allHaveError = partDownloaderList.values
@ -366,11 +371,11 @@ class DownloadJob(
.all {
it.injured()
}
if (allHaveError) {
if (allHaveError && !paused) {
// println("all have error!")
scope.launch {
// println("request pause send")
pause(TooManyErrorException())
pause(TooManyErrorException(throwable))
}
}
}

View File

@ -1,3 +1,5 @@
package ir.amirab.downloader.exception
abstract class DownloadValidationException(msg:String):Exception(msg)
abstract class DownloadValidationException(msg:String):Exception(msg){
abstract fun isCritical():Boolean
}

View File

@ -2,16 +2,20 @@ package ir.amirab.downloader.exception
sealed class FileChangedException(msg:String, ):DownloadValidationException(msg){
override fun isCritical(): Boolean{
// download must stop immediately
return true
}
class LengthChangedException(
val lastContentLength: Long,
val newContentLength: Long
) : DownloadValidationException(
) : FileChangedException(
"File size changed since last download! last time was $lastContentLength now it's $newContentLength"
)
class ETagChangedException(
val oldETag: String,
val newETag: String
) : DownloadValidationException(
) : FileChangedException(
"File content changed since last download! last time was $oldETag now it's $newETag"
)
}

View File

@ -5,4 +5,9 @@ class NoSpaceInStorageException(
val required: Long
) : DownloadValidationException(
"No space available required=$required , available=$available"
)
) {
override fun isCritical(): Boolean {
// there is no space in users file system so we should stop
return true
}
}

View File

@ -1,4 +1,21 @@
package ir.amirab.downloader.exception
//it should not happened unless web server is not respect our header
class ServerPartIsNotTheSameAsWeExpectException(msg: String) : DownloadValidationException(msg)
//it should not happen unless web server is not respect our header
class ServerPartIsNotTheSameAsWeExpectException(
start:Long,
end:Long?,
expectedLength:Long?,
actualLength:Long?,
) : DownloadValidationException (
"Response Length not match.expecting '${expectedLength}',but we got '$actualLength',requested range is range is ${start}-${end}"
// + "\n request headers ${conn.responseInfo.requestHeaders}"
// + "\n response headers ${conn.responseInfo.responseHeaders}"
){
override fun isCritical(): Boolean {
// some webservers somehow does not return the expected size at the first place
// but after some try... they do!!!
// because of them, I have to make this error non-critical
// I have to investigate why!
return false
}
}

View File

@ -1,5 +1,8 @@
package ir.amirab.downloader.exception
class TooManyErrorException : Exception(
"Download is stopped because all parts exceeds max retries"
class TooManyErrorException(
lastException: Throwable,
) : Exception(
"Download is stopped because all parts exceeds max retries",
lastException,
)

View File

@ -121,14 +121,12 @@ class PartDownloader(
} catch (e: Exception) {
tries++
onCanceled(e)
if (!canRetry(e)) {
if (e is DownloadValidationException) {
iCantRetryAnymore(e)
}
break
} else {
continue
when(canRetry(e)){
CanRetryResult.Yes -> continue
CanRetryResult.No -> {}
CanRetryResult.NoAndStopDownloadJob -> iCantRetryAnymore(e)
}
break
}
//download progress started, but maybe we have errors
//wait for a finish/error event...
@ -138,16 +136,13 @@ class PartDownloader(
}
when (status) {
is PartDownloadStatus.Canceled -> {
if (!canRetry(status.e)) {
if (status.e is DownloadValidationException) {
iCantRetryAnymore(status.e)
}
break
} else {
tries++
continue
tries++
when(canRetry(status.e)){
CanRetryResult.Yes -> continue
CanRetryResult.No -> {}
CanRetryResult.NoAndStopDownloadJob -> iCantRetryAnymore(status.e)
}
break
}
PartDownloadStatus.Completed -> break
@ -169,15 +164,26 @@ class PartDownloader(
}
}
private fun canRetry(e: Throwable): Boolean {
private sealed interface CanRetryResult{
data object Yes:CanRetryResult
data object No:CanRetryResult
data object NoAndStopDownloadJob:CanRetryResult
}
private fun canRetry(e: Throwable): CanRetryResult {
return when {
ExceptionUtils.isNormalCancellation(e) -> {
false
CanRetryResult.No
}
e is DownloadValidationException -> if (e.isCritical()){
//download validation occurs, and also it is critical,
//so we can't proceed any further
CanRetryResult.NoAndStopDownloadJob
}else{
CanRetryResult.Yes
}
e is DownloadValidationException -> false
else -> {
true
CanRetryResult.Yes
}
}
}
@ -225,9 +231,10 @@ class PartDownloader(
if (contentLength != partCopy.remainingLength) {
conn.closeable.close()
throw ServerPartIsNotTheSameAsWeExpectException(
"part remaining length: ${part.remainingLength} and response length: ${conn.contentLength} not match"
+ "\n request headers ${conn.responseInfo.requestHeaders}"
+ "\n response headers ${conn.responseInfo.responseHeaders}"
start = partCopy.current,
end = partCopy.to,
expectedLength = partCopy.remainingLength,
actualLength = contentLength
)
}
thread = thread {