Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e8e2c2a
Fix: Indicate correct error message in case of loss of internet conne…
RaresNicoMoldo Mar 12, 2026
13d32ec
Fix: Indicate correct error message in case of loss of internet conne…
RaresNicoMoldo Mar 14, 2026
5489899
Fix: Indicate correct error message in case of loss of internet conne…
RaresNicoMoldo Mar 14, 2026
794eb37
Merge branch 'commons-app:main' into main
20020316 Mar 14, 2026
0d56317
Merge branch 'main' of https://github.com/20020316/apps-android-commons
RaresNicoMoldo Mar 14, 2026
455c1ba
Update gradle-wrapper.properties
20020316 Mar 14, 2026
55d76f8
Fix: Indicate correct error message in case of loss of internet conne…
RaresNicoMoldo Mar 15, 2026
80bbc1b
Merge branch 'main' of https://github.com/20020316/apps-android-commons
RaresNicoMoldo Mar 15, 2026
6c1c992
Update UploadClient.kt
20020316 Mar 15, 2026
84c0804
Fix: Indicate correct error message in case of loss of internet conne…
RaresNicoMoldo Mar 15, 2026
0fd4a9e
Merge branch 'main' of https://github.com/20020316/apps-android-commons
RaresNicoMoldo Mar 15, 2026
1fc9ab1
Update gradle-wrapper.properties
20020316 Mar 15, 2026
a7fce6d
Added strings to xml, uniform indentation, and context added for reso…
RaresNicoMoldo Mar 15, 2026
182a6fb
Update strings.xml
20020316 Mar 15, 2026
459bcea
Update gradle-wrapper.properties
20020316 Mar 17, 2026
a860667
Reverted indentation to original(unmodified) file indentation.
RaresNicoMoldo Mar 17, 2026
aeffd86
Merge branch 'main' of https://github.com/20020316/apps-android-commons
RaresNicoMoldo Mar 17, 2026
65a5569
Indentation fix for uploadFileToStash, class parameters and class pri…
RaresNicoMoldo Mar 18, 2026
1f730b7
Update UploadClient.kt
20020316 Mar 19, 2026
6dcc6ec
Update UploadClient.kt
20020316 Mar 19, 2026
31fb5be
Update UploadClient.kt
20020316 Mar 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 56 additions & 32 deletions app/src/main/java/fr/free/nrw/commons/upload/UploadClient.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package fr.free.nrw.commons.upload


import com.google.gson.Gson
import com.google.gson.JsonObject
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.contributions.ChunkInfo
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
Expand All @@ -22,15 +24,17 @@ import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.URLEncoder
import java.net.UnknownHostException
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton

@Singleton
class UploadClient
@Inject
Expand Down Expand Up @@ -134,7 +138,7 @@ class UploadClient
}
else -> {
Timber.d("Upload stash failed %s", contribution.pageId)
Observable.just(StashUploadResult(StashUploadState.FAILED, null, null))
Observable.just(StashUploadResult(StashUploadState.FAILED, null, "Network error"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure it fails here due to a network error only?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I do not know why it remained a network error. I had reversed it. I will commit again with null instead.

}
}
}
Expand Down Expand Up @@ -178,18 +182,28 @@ class UploadClient
filekey,
countingRequestBody,
).subscribe(
{ uploadResult: UploadResult ->
{ uploadResult: UploadResult? ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uploadChunkToStash returns Observable<UploadResult>. It's not a nullable, is there anything that I'm missing out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the uploadChunkToStash returned Observable<uploadResult> previously because the try catch block handled all exceptions as one, rather than dealing with them separately, so the compiler did not complain. The current code explicitly handles errors; it's better to handle the null exception separately from network errors. And to add, uploadChunksToStash maps its result to a variable of type UploadResult?, which is declared as such in UploadResponse.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this PR adds onErrorResumeNext to the code, which makes it a nullable. Could you please elaborate in the PR description why this is necessary to fix the issue?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the modified PR description per your request.

Timber.d(
"Chunk: Received Chunk number: %s, offset: %s",
index.get(),
uploadResult.offset,
uploadResult?.offset,
)
chunkInfo.set(ChunkInfo(uploadResult, index.get(), totalChunks))
notificationUpdater.onChunkUploaded(contribution, chunkInfo.get())
},
{ throwable: Throwable? ->
Timber.e(throwable, "Received error in chunk upload")
errorMessage.set(throwable?.message)

val message = when (throwable) {
is InvalidLoginTokenException ->"The session token was not found by the"+
" server."
is UnknownHostException -> "Could not contact the remote host."
is ConnectException -> "The connection was interrupted while uploading."
is SocketTimeoutException -> "The socket operation timed out."
else -> throwable?.message ?: "An unexpected error occurred."
}

errorMessage.set(message)
failures.set(true)
},
),
Expand Down Expand Up @@ -226,28 +240,33 @@ class UploadClient
offset: Long,
fileKey: String?,
countingRequestBody: CountingRequestBody,
): Observable<UploadResult> {
val filePart: MultipartBody.Part
return try {
filePart =
MultipartBody.Part.createFormData(
"chunk",
URLEncoder.encode(filename, "utf-8"),
countingRequestBody,
)
uploadInterface
.uploadFileToStash(
toRequestBody(filename),
toRequestBody(fileSize.toString()),
toRequestBody(offset.toString()),
toRequestBody(fileKey),
toRequestBody(csrfTokenClient.getTokenBlocking()),
filePart,
).map(UploadResponse::upload)
} catch (throwable: Throwable) {
Timber.e(throwable, "Failed to upload chunk to stash")
Observable.error(throwable)
}
): Observable<UploadResult?> {
val filePart = MultipartBody.Part.createFormData(
"chunk",
URLEncoder.encode(filename, "utf-8"),
countingRequestBody
)

return uploadInterface
.uploadFileToStash(
toRequestBody(filename),
toRequestBody(fileSize.toString()),
toRequestBody(offset.toString()),
toRequestBody(fileKey),
toRequestBody(csrfTokenClient.getTokenBlocking()),
filePart
)
.map(UploadResponse::upload)
.onErrorResumeNext { e: Throwable ->
Timber.e(e, when (e) {
is InvalidLoginTokenException -> "The server could not retrieve the session token."
is UnknownHostException -> "Could not contact the remote host."
is SocketTimeoutException -> "The socket operation timed out while uploading."
is ConnectException -> "The connection was interrupted while uploading a chunk."
else -> "Unexpected error during chunk upload."
})
Observable.error(e)
}
}

/**
Expand All @@ -260,7 +279,6 @@ class UploadClient
uniqueFileName: String?,
fileKey: String?,
): Observable<UploadResult?> =
try {
uploadInterface
.uploadFileFromStash(
csrfTokenClient.getTokenBlocking(),
Expand All @@ -276,11 +294,17 @@ class UploadClient
throw Exception(exception.errorCode)
}
uploadResult.upload
}.
onErrorResumeNext { e: Throwable ->
Timber.e(e, when (e) {
is InvalidLoginTokenException -> "The server could not retrieve the session token."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about writing a helper function for this common code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed.

is UnknownHostException -> "Could not contact the remote host."
is SocketTimeoutException -> "The socket connection timed out."
is ConnectException -> "The connection was interrupted while uploading."
else -> "An unexpected error occurred."
})
Observable.error(e)
}
} catch (throwable: Throwable) {
Timber.e(throwable, "Exception occurred in uploading file from stash")
Observable.error(throwable)
}
}

private fun canProcess(
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Sun Apr 23 18:22:54 IST 2023
#Sat Feb 28 23:53:32 CET 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
Expand Down