diff --git a/app/build.gradle b/app/build.gradle
index f4ce47239b..a7b9cf4c61 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -142,7 +142,7 @@ dependencies {
implementation "androidx.multidex:multidex:$MULTIDEX_VERSION"
- def work_version = "2.8.0"
+ def work_version = "2.8.1"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation("androidx.work:work-runtime:$work_version")
@@ -168,7 +168,7 @@ project.gradle.taskGraph.whenReady {
}
android {
- compileSdkVersion 31
+ compileSdkVersion 33
defaultConfig {
//applicationId 'fr.free.nrw.commons'
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
index c3d084f0af..28e5892bd3 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
@@ -43,7 +43,12 @@ data class Contribution constructor(
var hasInvalidLocation : Int = 0,
var contentUri: Uri? = null,
var countryCode : String? = null,
- var imageSHA1 : String? = null
+ var imageSHA1 : String? = null,
+ /**
+ * Number of times a contribution has been retried after a failure
+ */
+ var retries: Int = 0,
+ var wikidataUpdateWasSuccessful: Boolean = false
) : Parcelable {
fun completeWith(media: Media): Contribution {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
index 03e3e56116..8276f9c741 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
@@ -29,10 +29,13 @@
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.media.MediaClient;
import fr.free.nrw.commons.notification.models.Notification;
import fr.free.nrw.commons.notification.NotificationController;
import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.theme.BaseActivity;
+import fr.free.nrw.commons.upload.FileUtilsWrapper;
+import java.io.FileNotFoundException;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
@@ -85,6 +88,10 @@ public class ContributionsFragment
@Inject CampaignsPresenter presenter;
@Inject LocationServiceManager locationManager;
@Inject NotificationController notificationController;
+ @Inject
+ MediaClient mediaClient;
+ @Inject
+ FileUtilsWrapper fileUtilsWrapper;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@@ -92,6 +99,7 @@ public class ContributionsFragment
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
private MediaDetailPagerFragment mediaDetailPagerFragment;
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
+ private static final int MAX_RETRIES = 10;
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
@BindView(R.id.campaigns_view) CampaignView campaignView;
@@ -593,6 +601,54 @@ public void notifyDataSetChanged() {
}
}
+ /**
+ * Restarts the upload process for a contribution
+ * @param contribution
+ */
+ private void restartUpload(Contribution contribution) {
+ contribution.setState(Contribution.STATE_QUEUED);
+ contributionsPresenter.saveContribution(contribution);
+ Timber.d("Restarting for %s", contribution.toString());
+ }
+
+ /**
+ * Retry for a failed contribution only if it does not exist on the server
+ * @param contribution
+ * @param retries
+ */
+ private void retryFailedUpload(Contribution contribution, int retries) {
+ String filePath = contribution.getLocalUri().getPath();
+ String fileSHA;
+ try {
+ fileSHA = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(filePath));
+ } catch (FileNotFoundException e) {
+ fileSHA = null;
+ }
+ boolean fileExists = mediaClient
+ .checkFileExistsUsingSha(fileSHA)
+ .subscribeOn(Schedulers.io()).blockingGet();
+ if (fileExists && !contribution.getWikidataUpdateWasSuccessful()) {
+ Timber.d("%s exists on the server already but lacks caption",
+ contribution.getMedia().getFilename());
+ // Show a popup to the user to edit the caption
+ DialogUtil.showAlertDialog(
+ getActivity(),
+ getString(R.string.wikidata_edit_failed_title),
+ getString(R.string.wikidata_edit_failed_explanation, contribution.getMedia().getFilename()),
+ getString(R.string.ok),
+ () -> {},
+ true
+ );
+ contribution.setState(Contribution.STATE_COMPLETED);
+ contributionsPresenter.saveContribution(contribution);
+ } else {
+ contribution.setRetries(retries + 1);
+ Timber.d("Retried uploading %s %d times",
+ contribution.getMedia().getFilename(), retries + 1);
+ restartUpload(contribution);
+ }
+ }
+
/**
* Retry upload when it is failed
*
@@ -601,10 +657,20 @@ public void notifyDataSetChanged() {
@Override
public void retryUpload(Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
- if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
- contribution.setState(Contribution.STATE_QUEUED);
- contributionsPresenter.saveContribution(contribution);
- Timber.d("Restarting for %s", contribution.toString());
+ if (contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
+ restartUpload(contribution);
+ } else if (contribution.getState() == STATE_FAILED) {
+ int retries = contribution.getRetries();
+ /* Limit the number of retries for a failed upload
+ to handle cases like invalid filename as such uploads
+ will never be successful */
+ if(retries < MAX_RETRIES) {
+ retryFailedUpload(contribution, retries);
+ } else {
+ // TODO: Show the exact reason for failure
+ Toast.makeText(getContext(),
+ R.string.retry_limit_reached, Toast.LENGTH_SHORT).show();
+ }
} else {
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
index 002e8bc95b..e6fdaa11ec 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
@@ -1,7 +1,11 @@
package fr.free.nrw.commons.contributions;
+import androidx.work.BackoffPolicy;
+import androidx.work.Constraints;
import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
@@ -9,10 +13,7 @@
import fr.free.nrw.commons.upload.worker.UploadWorker;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.functions.Action;
-import io.reactivex.functions.Consumer;
-import java.util.Collections;
-import java.util.List;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
@@ -77,10 +78,18 @@ public void saveContribution(Contribution contribution) {
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> {
+ Constraints constraints = new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+ OneTimeWorkRequest updatedUploadRequest = new OneTimeWorkRequest
+ .Builder(UploadWorker.class)
+ .setConstraints(constraints)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
+ .build();
WorkManager.getInstance(view.getContext().getApplicationContext())
.enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
- ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
+ ExistingWorkPolicy.KEEP, updatedUploadRequest);
}));
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
index a96f1f37bd..18a5709309 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.contributions;
import android.Manifest.permission;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -17,8 +18,12 @@
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.work.BackoffPolicy;
+import androidx.work.Constraints;
import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -47,6 +52,9 @@
import fr.free.nrw.commons.upload.worker.UploadWorker;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
+import io.reactivex.schedulers.Schedulers;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
@@ -58,6 +66,8 @@ public class MainActivity extends BaseActivity
SessionManager sessionManager;
@Inject
ContributionController controller;
+ @Inject
+ ContributionDao contributionDao;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.pager)
@@ -138,6 +148,9 @@ public void onCreate(Bundle savedInstanceState) {
setTitle(getString(R.string.navigation_item_explore));
setUpLoggedOutPager();
} else {
+ if (applicationKvStore.getBoolean("firstrun", true)) {
+ applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false);
+ }
if(savedInstanceState == null){
//starting a fresh fragment.
// Open Last opened screen if it is Contributions or Nearby, otherwise Contributions
@@ -360,6 +373,21 @@ public boolean onOptionsItemSelected(MenuItem item) {
}
}
+ /**
+ * Retry all failed uploads as soon as the user returns to the app
+ */
+ @SuppressLint("CheckResult")
+ private void retryAllFailedUploads() {
+ contributionDao.
+ getContribution(Collections.singletonList(Contribution.STATE_FAILED))
+ .subscribeOn(Schedulers.io())
+ .subscribe(failedUploads -> {
+ for (Contribution contribution: failedUploads) {
+ contributionsFragment.retryUpload(contribution);
+ }
+ });
+ }
+
public void toggleLimitedConnectionMode() {
defaultKvStore.putBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
!defaultKvStore
@@ -369,9 +397,17 @@ public void toggleLimitedConnectionMode() {
viewUtilWrapper
.showShortToast(getBaseContext(), getString(R.string.limited_connection_enabled));
} else {
+ Constraints constraints = new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+ OneTimeWorkRequest restartUploadsRequest = new OneTimeWorkRequest
+ .Builder(UploadWorker.class)
+ .setConstraints(constraints)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
+ .build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
- ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));
+ ExistingWorkPolicy.APPEND_OR_REPLACE, restartUploadsRequest);
viewUtilWrapper
.showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled));
@@ -405,6 +441,8 @@ protected void onResume() {
(!applicationKvStore.getBoolean("login_skipped"))) {
WelcomeActivity.startYourself(this);
}
+
+ retryAllFailedUploads();
}
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
index 89cbb8fb40..f454a3af8c 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt
@@ -16,7 +16,7 @@ open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {
private val SWIPE_THRESHOLD_WIDTH = (getScreenResolution(context!!)).first / 3
private val SWIPE_VELOCITY_THRESHOLD = 1000
- override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean {
+ override fun onTouch(view: View?, motionEvent: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(motionEvent)
}
@@ -32,7 +32,7 @@ open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {
inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
- override fun onDown(e: MotionEvent?): Boolean {
+ override fun onDown(e: MotionEvent): Boolean {
return true
}
diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
index 49c95343a5..6d63e58a1c 100644
--- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
+++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
@@ -15,7 +15,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
* The database for accessing the respective DAOs
*
*/
-@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 15, exportSchema = false)
+@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class], version = 16, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
index 7fd4ecce7f..8279f8e74c 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
@@ -1,21 +1,21 @@
package fr.free.nrw.commons.upload;
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
-import static fr.free.nrw.commons.upload.UploadPresenter.COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
-import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -24,8 +24,12 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
+import androidx.work.BackoffPolicy;
+import androidx.work.Constraints;
import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
+import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -35,7 +39,6 @@
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionController;
-import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.UserClient;
@@ -57,6 +60,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
@@ -317,9 +321,17 @@ public void updateTopCardTitle() {
@Override
public void makeUploadRequest() {
+ Constraints constraints = new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+ OneTimeWorkRequest uploadRequest = new OneTimeWorkRequest
+ .Builder(UploadWorker.class)
+ .setConstraints(constraints)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
+ .build();
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
UploadWorker.class.getSimpleName(),
- ExistingWorkPolicy.APPEND_OR_REPLACE, OneTimeWorkRequest.from(UploadWorker.class));
+ ExistingWorkPolicy.APPEND_OR_REPLACE, uploadRequest);
}
@Override
@@ -364,6 +376,42 @@ private void receiveSharedItems() {
.getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size()));
fragments = new ArrayList<>();
+ /* Suggest users to turn battery optimisation off when uploading more than a few files.
+ That's because we have noticed that many-files uploads have
+ a much higher probability of failing than uploads with less files.
+
+ Show the dialog for Android 6 and above as
+ the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent was added in API level 23
+ */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (uploadableFiles.size() > 3
+ && !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")) {
+ DialogUtil.showAlertDialog(
+ this,
+ getString(R.string.unrestricted_battery_mode),
+ getString(R.string.suggest_unrestricted_mode),
+ getString(R.string.title_activity_settings),
+ getString(R.string.cancel),
+ () -> {
+ /* Since opening the right settings page might be device dependent, using
+ https://github.com/WaseemSabir/BatteryPermissionHelper
+ directly appeared like a promising idea.
+ However, this simply closed the popup and did not make
+ the settings page appear on a Pixel as well as a Xiaomi device.
+
+ Used the standard intent instead of using this library as
+ it shows a list of all the apps on the device and allows users to
+ turn battery optimisation off.
+ */
+ Intent batteryOptimisationSettingsIntent = new Intent(
+ Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
+ startActivity(batteryOptimisationSettingsIntent);
+ },
+ () -> {}
+ );
+ defaultKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", true);
+ }
+ }
for (UploadableFile uploadableFile : uploadableFiles) {
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place);
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index 6a4497ea15..4761f48c5d 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -1,17 +1,20 @@
package fr.free.nrw.commons.upload.worker
import android.annotation.SuppressLint
+import android.app.Notification
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
+import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import androidx.multidex.BuildConfig
+import androidx.work.ForegroundInfo
import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.Media
@@ -40,6 +43,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
+import java.net.SocketTimeoutException
import java.util.*
import java.util.regex.Pattern
import javax.inject.Inject
@@ -160,73 +164,99 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
}
override suspend fun doWork(): Result {
- var countUpload = 0
- notificationManager = NotificationManagerCompat.from(appContext)
- val processingUploads = getNotificationBuilder(
- CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL
- )!!
- withContext(Dispatchers.IO) {
- val queuedContributions = contributionDao.getContribution(statesToProcess)
- .blockingGet()
- //Showing initial notification for the number of uploads being processed
-
- Timber.e("Queued Contributions: "+ queuedContributions.size)
-
- processingUploads.setContentTitle(appContext.getString(R.string.starting_uploads))
- processingUploads.setContentText(
- appContext.resources.getQuantityString(
- R.plurals.starting_multiple_uploads,
- queuedContributions.size,
- queuedContributions.size
- )
- )
- notificationManager?.notify(
- PROCESSING_UPLOADS_NOTIFICATION_TAG,
- PROCESSING_UPLOADS_NOTIFICATION_ID,
- processingUploads.build()
- )
+ try {
+ var countUpload = 0
+ // Start a foreground service
+ setForeground(createForegroundInfo())
+ notificationManager = NotificationManagerCompat.from(appContext)
+ val processingUploads = getNotificationBuilder(
+ CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL
+ )!!
+ withContext(Dispatchers.IO) {
+ val queuedContributions = contributionDao.getContribution(statesToProcess)
+ .blockingGet()
+ //Showing initial notification for the number of uploads being processed
- /**
- * To avoid race condition when multiple of these workers are working, assign this state
- so that the next one does not process these contribution again
- */
- queuedContributions.forEach {
- it.state=Contribution.STATE_IN_PROGRESS
- contributionDao.saveSynchronous(it)
- }
+ Timber.e("Queued Contributions: " + queuedContributions.size)
+
+ processingUploads.setContentTitle(appContext.getString(R.string.starting_uploads))
+ processingUploads.setContentText(
+ appContext.resources.getQuantityString(
+ R.plurals.starting_multiple_uploads,
+ queuedContributions.size,
+ queuedContributions.size
+ )
+ )
+ notificationManager?.notify(
+ PROCESSING_UPLOADS_NOTIFICATION_TAG,
+ PROCESSING_UPLOADS_NOTIFICATION_ID,
+ processingUploads.build()
+ )
- queuedContributions.asFlow().map { contribution ->
/**
- * If the limited connection mode is on, lets iterate through the queued
- * contributions
- * and set the state as STATE_QUEUED_LIMITED_CONNECTION_MODE ,
- * otherwise proceed with the upload
+ * To avoid race condition when multiple of these workers are working, assign this state
+ so that the next one does not process these contribution again
*/
- if (isLimitedConnectionModeEnabled()) {
- if (contribution.state == Contribution.STATE_QUEUED) {
- contribution.state = Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE
+ queuedContributions.forEach {
+ it.state = Contribution.STATE_IN_PROGRESS
+ contributionDao.saveSynchronous(it)
+ }
+
+ queuedContributions.asFlow().map { contribution ->
+ /**
+ * If the limited connection mode is on, lets iterate through the queued
+ * contributions
+ * and set the state as STATE_QUEUED_LIMITED_CONNECTION_MODE ,
+ * otherwise proceed with the upload
+ */
+ if (isLimitedConnectionModeEnabled()) {
+ if (contribution.state == Contribution.STATE_QUEUED) {
+ contribution.state = Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE
+ contributionDao.saveSynchronous(contribution)
+ }
+ } else {
+ contribution.transferred = 0
+ contribution.state = Contribution.STATE_IN_PROGRESS
contributionDao.saveSynchronous(contribution)
+ setProgressAsync(Data.Builder().putInt("progress", countUpload).build())
+ countUpload++
+ uploadContribution(contribution = contribution)
}
- } else {
- contribution.transferred = 0
- contribution.state = Contribution.STATE_IN_PROGRESS
- contributionDao.saveSynchronous(contribution)
- setProgressAsync(Data.Builder().putInt("progress", countUpload).build())
- countUpload++
- uploadContribution(contribution = contribution)
- }
- }.collect()
+ }.collect()
- //Dismiss the global notification
- notificationManager?.cancel(
- PROCESSING_UPLOADS_NOTIFICATION_TAG,
- PROCESSING_UPLOADS_NOTIFICATION_ID
- )
+ //Dismiss the global notification
+ notificationManager?.cancel(
+ PROCESSING_UPLOADS_NOTIFICATION_TAG,
+ PROCESSING_UPLOADS_NOTIFICATION_ID
+ )
+ }
+ //TODO make this smart, think of handling retries in the future
+ return Result.success()
+ } catch (e: Exception) {
+ return Result.retry()
}
- //TODO make this smart, think of handling retries in the future
- return Result.success()
}
+ /**
+ * Create new notification for foreground service
+ */
+ private fun createForegroundInfo(): ForegroundInfo {
+ return ForegroundInfo(
+ 1,
+ createNotificationForForegroundService()
+ )
+ }
+
+ override suspend fun getForegroundInfo(): ForegroundInfo {
+ return createForegroundInfo()
+ }
+ private fun createNotificationForForegroundService(): Notification {
+ // TODO: Improve notification for foreground service
+ return getNotificationBuilder(
+ CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)!!
+ .setContentTitle(appContext.getString(R.string.upload_in_progress))
+ .build()
+ }
/**
* Returns true is the limited connection mode is enabled
*/
@@ -379,6 +409,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
contribution.media.captions
)
if (null != revisionID) {
+ contribution.wikidataUpdateWasSuccessful = true
showSuccessNotification(contribution)
}
}catch (exception: Exception){
@@ -540,7 +571,12 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
val intent = Intent(appContext,toClass)
return TaskStackBuilder.create(appContext).run {
addNextIntentWithParentStack(intent)
- getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ getPendingIntent(0,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ } else {
+ getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
};
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e402e0c1ca..647d20e81b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -62,6 +62,9 @@
•
Settings
Upload to Commons
+ Upload in progress
+ Wikidata edit failed
+ Wikidata edit failed for the contribution %s. Please edit its caption and depiction.
Username
Password
Log in to your Commons Beta account
@@ -75,6 +78,9 @@
Login success!
Login failed!
File not found. Please try another file.
+ Maximum retry limit reached! Please cancel the upload and try again
+ Turn battery optimization off?
+ Uploading more than 3 images works more reliably when the battery optimization is turned off. Please turn battery optimization off for the Commons app from the settings for a smooth upload experience. \n\nPossible steps to turn battery optimization off:\n\nStep 1: Tap on the \'Settings\' button below.\n\nStep 2: Switch from \'Not optimized\' to \'All apps\'.\n\nStep 3: Search for \"Commons\" or \"fr.free.nrw.commons\".\n\nStep 4: Tap it and select \'Don\'t optimize\'.\n\nStep 5: Press \'Done\'.
Authentication failed, please login again
Upload started!
Upload queued (limited connection mode enabled)