diff --git a/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.java b/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.java new file mode 100644 index 000000000..f047bb6cb --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/CountingRequestBody.java @@ -0,0 +1,86 @@ +package fr.free.nrw.commons.upload; + + +import java.io.IOException; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.Buffer; +import okio.BufferedSink; +import okio.ForwardingSink; +import okio.Okio; +import okio.Sink; + +/** + * Decorates an OkHttp request body to count the number of bytes written when writing it. Can + * decorate any request body, but is most useful for tracking the upload progress of large multipart + * requests. + * + * @author Ashish Kumar + */ +public class CountingRequestBody extends RequestBody { + + protected RequestBody delegate; + protected Listener listener; + + protected CountingSink countingSink; + + public CountingRequestBody(RequestBody delegate, Listener listener) { + this.delegate = delegate; + this.listener = listener; + } + + @Override + public MediaType contentType() { + return delegate.contentType(); + } + + @Override + public long contentLength() { + try { + return delegate.contentLength(); + } catch (IOException e) { + e.printStackTrace(); + } + return -1; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + + countingSink = new CountingSink(sink); + BufferedSink bufferedSink = Okio.buffer(countingSink); + + delegate.writeTo(bufferedSink); + + bufferedSink.flush(); + } + + protected final class CountingSink extends ForwardingSink { + + private long bytesWritten = 0; + + public CountingSink(Sink delegate) { + super(delegate); + } + + @Override + public void write(Buffer source, long byteCount) throws IOException { + super.write(source, byteCount); + + bytesWritten += byteCount; + listener.onRequestProgress(bytesWritten, contentLength()); + } + + } + + public interface Listener { + + /** + * Will be triggered when write progresses + * @param bytesWritten + * @param contentLength + */ + void onRequestProgress(long bytesWritten, long contentLength); + } + +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java index 87f1b69ed..ca369e955 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java @@ -1,23 +1,20 @@ package fr.free.nrw.commons.upload; +import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; + import android.content.Context; import android.net.Uri; - -import org.wikipedia.csrf.CsrfTokenClient; - +import fr.free.nrw.commons.contributions.Contribution; +import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener; +import io.reactivex.Observable; import java.io.File; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; - -import fr.free.nrw.commons.contributions.Contribution; -import io.reactivex.Observable; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; - -import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; +import org.wikipedia.csrf.CsrfTokenClient; @Singleton public class UploadClient { @@ -31,11 +28,16 @@ public class UploadClient { this.csrfTokenClient = csrfTokenClient; } - Observable uploadFileToStash(Context context, String filename, File file) { - RequestBody requestFile = RequestBody + Observable uploadFileToStash(Context context, String filename, File file, + NotificationUpdateProgressListener notificationUpdater) { + RequestBody requestBody = RequestBody .create(MediaType.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath()))), file); - MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", filename, requestFile); + CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody, + (bytesWritten, contentLength) -> notificationUpdater + .onProgress(bytesWritten, contentLength)); + + MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", filename, countingRequestBody); RequestBody fileNameRequestBody = RequestBody.create(okhttp3.MultipartBody.FORM, filename); RequestBody tokenRequestBody; try { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index c9e5a2ee8..897c025ed 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -8,23 +8,8 @@ import android.content.Intent; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; -import android.widget.Toast; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.inject.Inject; - import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.HandlerService; @@ -40,6 +25,13 @@ import fr.free.nrw.commons.utils.CommonsDateUtil; import fr.free.nrw.commons.wikidata.WikidataEditService; import io.reactivex.Observable; import io.reactivex.schedulers.Schedulers; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; import timber.log.Timber; public class UploadService extends HandlerService { @@ -80,7 +72,7 @@ public class UploadService extends HandlerService { super("UploadService"); } - private class NotificationUpdateProgressListener implements MediaWikiApi.ProgressListener { + protected class NotificationUpdateProgressListener implements MediaWikiApi.ProgressListener { String notificationTag; boolean notificationTitleChanged; @@ -202,29 +194,20 @@ public class UploadService extends HandlerService { @SuppressLint("CheckResult") private void uploadContribution(Contribution contribution) { - InputStream fileInputStream; Uri localUri = contribution.getLocalUri(); if (localUri == null || localUri.getPath() == null) { Timber.d("localUri/path is null"); return; } String notificationTag = localUri.toString(); - File file1; - try { - file1 = new File(localUri.getPath()); - fileInputStream = new FileInputStream(file1); - } catch (FileNotFoundException e) { - Timber.d("File not found"); - Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG); - fileNotFound.show(); - return; - } + File localFile = new File(localUri.getPath()); Timber.d("Before execution!"); curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start, contribution.getDisplayTitle())) .setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)) .setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle())); - notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); + notificationManager + .notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build()); String filename = contribution.getFilename(); @@ -235,7 +218,9 @@ public class UploadService extends HandlerService { ); Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename) - .flatMap(stashFilename -> uploadClient.uploadFileToStash(getApplicationContext(), stashFilename, file1)) + .flatMap(stashFilename -> uploadClient + .uploadFileToStash(getApplicationContext(), stashFilename, localFile, + notificationUpdater)) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .doFinally(() -> { @@ -250,7 +235,7 @@ public class UploadService extends HandlerService { } }) .flatMap(uploadStash -> { - notificationManager.cancel(NOTIFICATION_UPLOAD_IN_PROGRESS); + notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); Timber.d("Stash upload response 1 is %s", uploadStash.toString()); @@ -293,7 +278,7 @@ public class UploadService extends HandlerService { } }, throwable -> { Timber.w(throwable, "Exception during upload"); - notificationManager.cancel(NOTIFICATION_UPLOAD_IN_PROGRESS); + notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); showFailedNotification(contribution); }); }