Retry failed uploads - #1556 Allow users to easily re-upload failed uploads... (#2112)

* Add cancel and retry buttons on layout contribution

* Make sure your retry logic works

* Add cancel method too

* Add javadocs and remove debug logs

* Remove two unused methods

* Remove old and unused retry buttons as we do for their functions

* Check internet connection before button function, since function requires internet connection

* Remove unused variable

* Display possible solution for badtoken error

* Fix string
This commit is contained in:
Josephine Lim 2018-12-20 02:56:00 +10:00 committed by GitHub
commit 21edcb7cbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 169 additions and 85 deletions

View file

@ -1,6 +1,8 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
@ -13,6 +15,9 @@ class ContributionViewHolder {
final TextView stateView; final TextView stateView;
final TextView seqNumView; final TextView seqNumView;
final ProgressBar progressView; final ProgressBar progressView;
final ImageButton retryButton;
final ImageButton cancelButton;
int position;
ContributionViewHolder(View parent) { ContributionViewHolder(View parent) {
imageView = parent.findViewById(R.id.contributionImage); imageView = parent.findViewById(R.id.contributionImage);
@ -20,5 +25,8 @@ class ContributionViewHolder {
stateView = parent.findViewById(R.id.contributionState); stateView = parent.findViewById(R.id.contributionState);
seqNumView = parent.findViewById(R.id.contributionSequenceNumber); seqNumView = parent.findViewById(R.id.contributionSequenceNumber);
progressView = parent.findViewById(R.id.contributionProgress); progressView = parent.findViewById(R.id.contributionProgress);
retryButton = parent.findViewById(R.id.retryButton);
cancelButton = parent.findViewById(R.id.cancelButton);
position = 0;
} }
} }

View file

@ -332,6 +332,8 @@ public class ContributionsFragment
contributionsListFragment.clearSyncMessage(); contributionsListFragment.clearSyncMessage();
notifyAndMigrateDataSetObservers(); notifyAndMigrateDataSetObservers();
((ContributionsListAdapter)contributionsListFragment.getAdapter()).setUploadService(uploadService);
} }
} }
@ -421,36 +423,6 @@ public class ContributionsFragment
mediaDetailPagerFragment.showImage(i); mediaDetailPagerFragment.showImage(i);
} }
/**
* Retry upload when it is failed
* @param i position of upload which will be retried
*/
public void retryUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", c.toString());
}
}
/**
* Delete a failed upload attempt
* @param i position of upload attempt which will be deteled
*/
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toString());
contributionDao.delete(c);
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
}
}
@Override @Override
public void refreshSource() { public void refreshSource() {
getActivity().getSupportLoaderManager().restartLoader(0, null, this); getActivity().getSupportLoaderManager().restartLoader(0, null, this);

View file

@ -3,21 +3,33 @@ package fr.free.nrw.commons.contributions;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
class ContributionsListAdapter extends CursorAdapter { class ContributionsListAdapter extends CursorAdapter {
private final ContributionDao contributionDao; private final ContributionDao contributionDao;
private UploadService uploadService;
public ContributionsListAdapter(Context context, Cursor c, int flags, ContributionDao contributionDao) { public ContributionsListAdapter(Context context, Cursor c, int flags, ContributionDao contributionDao) {
super(context, c, flags); super(context, c, flags);
this.contributionDao = contributionDao; this.contributionDao = contributionDao;
} }
public void setUploadService( UploadService uploadService) {
this.uploadService = uploadService;
}
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View parent = LayoutInflater.from(context) View parent = LayoutInflater.from(context)
@ -36,21 +48,29 @@ class ContributionsListAdapter extends CursorAdapter {
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1)); views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
views.seqNumView.setVisibility(View.VISIBLE); views.seqNumView.setVisibility(View.VISIBLE);
views.position = cursor.getPosition();
switch (contribution.getState()) { switch (contribution.getState()) {
case Contribution.STATE_COMPLETED: case Contribution.STATE_COMPLETED:
views.stateView.setVisibility(View.GONE); views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.GONE); views.progressView.setVisibility(View.GONE);
views.retryButton.setVisibility(View.GONE);
views.cancelButton.setVisibility(View.GONE);
views.stateView.setText(""); views.stateView.setText("");
break; break;
case Contribution.STATE_QUEUED: case Contribution.STATE_QUEUED:
views.stateView.setVisibility(View.VISIBLE); views.stateView.setVisibility(View.VISIBLE);
views.progressView.setVisibility(View.GONE); views.progressView.setVisibility(View.GONE);
views.stateView.setText(R.string.contribution_state_queued); views.stateView.setText(R.string.contribution_state_queued);
views.retryButton.setVisibility(View.GONE);
views.cancelButton.setVisibility(View.GONE);
break; break;
case Contribution.STATE_IN_PROGRESS: case Contribution.STATE_IN_PROGRESS:
views.stateView.setVisibility(View.GONE); views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.VISIBLE); views.progressView.setVisibility(View.VISIBLE);
views.retryButton.setVisibility(View.GONE);
views.cancelButton.setVisibility(View.GONE);
long total = contribution.getDataLength(); long total = contribution.getDataLength();
long transferred = contribution.getTransferred(); long transferred = contribution.getTransferred();
if (transferred == 0 || transferred >= total) { if (transferred == 0 || transferred >= total) {
@ -63,7 +83,63 @@ class ContributionsListAdapter extends CursorAdapter {
views.stateView.setVisibility(View.VISIBLE); views.stateView.setVisibility(View.VISIBLE);
views.stateView.setText(R.string.contribution_state_failed); views.stateView.setText(R.string.contribution_state_failed);
views.progressView.setVisibility(View.GONE); views.progressView.setVisibility(View.GONE);
views.retryButton.setVisibility(View.VISIBLE);
views.cancelButton.setVisibility(View.VISIBLE);
views.retryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
retryUpload(cursor);
}
});
views.cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
deleteUpload(cursor);
}
});
break; break;
} }
} }
/**
* Retry upload when it is failed
* @param cursor cursor will be retried
*/
public void retryUpload(Cursor cursor) {
if (NetworkUtils.isInternetConnectionEstablished(mContext)) {
Contribution c = contributionDao.fromCursor(cursor);
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toString());
} else {
Timber.d("Skipping re-upload for non-failed %s", c.toString());
}
} else {
ViewUtil.showLongToast(mContext,R.string.this_function_needs_network_connection);
}
}
/**
* Delete a failed upload attempt
* @param cursor cursor which will be deleted
*/
public void deleteUpload(Cursor cursor) {
if (NetworkUtils.isInternetConnectionEstablished(mContext)) {
Contribution c = contributionDao.fromCursor(cursor);
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toString());
contributionDao.delete(c);
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
}
} else {
ViewUtil.showLongToast(mContext,R.string.this_function_needs_network_connection);
}
}
} }

View file

@ -189,16 +189,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
// Set wallpaper // Set wallpaper
setWallpaper(m); setWallpaper(m);
return true; return true;
case R.id.menu_retry_current_image:
// Retry
//((MainActivity) getActivity()).retryUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
case R.id.menu_cancel_current_image:
// todo: delete image
//((MainActivity) getActivity()).deleteUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -279,8 +269,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Media m = provider.getMediaAtPosition(pager.getCurrentItem()); Media m = provider.getMediaAtPosition(pager.getCurrentItem());
if (m != null) { if (m != null) {
// Enable default set of actions, then re-enable different set of actions only if it is a failed contrib // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
menu.findItem(R.id.menu_retry_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_cancel_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true);
menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true);
menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true);
@ -297,8 +285,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Contribution c = (Contribution) m; Contribution c = (Contribution) m;
switch (c.getState()) { switch (c.getState()) {
case Contribution.STATE_FAILED: case Contribution.STATE_FAILED:
menu.findItem(R.id.menu_retry_current_image).setEnabled(true).setVisible(true);
menu.findItem(R.id.menu_cancel_current_image).setEnabled(true).setVisible(true);
menu.findItem(R.id.menu_browser_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_browser_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_share_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_share_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_download_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_download_current_image).setEnabled(false).setVisible(false);
@ -306,8 +292,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
break; break;
case Contribution.STATE_IN_PROGRESS: case Contribution.STATE_IN_PROGRESS:
case Contribution.STATE_QUEUED: case Contribution.STATE_QUEUED:
menu.findItem(R.id.menu_retry_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_cancel_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_browser_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_browser_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_share_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_share_current_image).setEnabled(false).setVisible(false);
menu.findItem(R.id.menu_download_current_image).setEnabled(false).setVisible(false); menu.findItem(R.id.menu_download_current_image).setEnabled(false).setVisible(false);

View file

@ -8,6 +8,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -44,6 +45,7 @@ import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.PageTitle; import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.achievements.FeedbackResponse; import fr.free.nrw.commons.achievements.FeedbackResponse;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.category.CategoryImageUtils; import fr.free.nrw.commons.category.CategoryImageUtils;
@ -52,6 +54,7 @@ import fr.free.nrw.commons.notification.Notification;
import fr.free.nrw.commons.notification.NotificationUtils; import fr.free.nrw.commons.notification.NotificationUtils;
import fr.free.nrw.commons.utils.ContributionUtils; import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.DateUtils; import fr.free.nrw.commons.utils.DateUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import in.yuvi.http.fluent.Http; import in.yuvi.http.fluent.Http;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -81,6 +84,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private final String WIKIMEDIA_CAMPAIGNS_BASE_URL = private final String WIKIMEDIA_CAMPAIGNS_BASE_URL =
"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json"; "https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json";
private final String ERROR_CODE_BAD_TOKEN = "badtoken";
public ApacheHttpClientMediaWikiApi(Context context, public ApacheHttpClientMediaWikiApi(Context context,
String apiURL, String apiURL,
String wikidatApiURL, String wikidatApiURL,
@ -901,6 +906,10 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
if (!resultStatus.equals("Success")) { if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code"); String errorCode = result.getString("/api/error/@code");
Timber.e(errorCode); Timber.e(errorCode);
if (errorCode.equals(ERROR_CODE_BAD_TOKEN)) {
ViewUtil.showLongToast(context, R.string.bad_token_error_proposed_solution);
}
return new UploadResult(resultStatus, errorCode); return new UploadResult(resultStatus, errorCode);
} else { } else {
// If success we have to remove file from temp directory // If success we have to remove file from temp directory

View file

@ -10,6 +10,7 @@ import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -28,39 +28,83 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center|bottom" android:layout_gravity="center|bottom"
android:background="#AA000000" android:background="#AA000000"
android:orientation="vertical" android:weightSum="4"
android:padding="@dimen/small_gap"
> >
<ProgressBar
android:id="@+id/contributionProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ProgressBar"
android:indeterminateOnly="false"
android:max="100"
android:visibility="gone"
/>
<TextView <LinearLayout
android:id="@+id/contributionState" android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?android:textAppearanceSmall" android:layout_weight="3"
android:textColor="#FFFFFFFF" android:layout_gravity="center|bottom"
android:visibility="gone" android:orientation="vertical"
/> android:padding="@dimen/small_gap"
>
<ProgressBar
android:id="@+id/contributionProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ProgressBar"
android:indeterminateOnly="false"
android:max="100"
android:visibility="gone"
/>
<TextView <TextView
android:id="@+id/contributionTitle" android:id="@+id/contributionState"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceSmall"
android:textColor="#FFFFFFFF"
android:visibility="gone"
/>
<TextView
android:id="@+id/contributionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
style="?android:textAppearanceMedium"
android:maxLines="2"
android:ellipsize="end"
/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="#FFFFFFFF" android:layout_weight="1"
style="?android:textAppearanceMedium" android:orientation="horizontal"
android:maxLines="2" android:padding="@dimen/small_gap"
android:ellipsize="end" >
/>
<ImageButton
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_cancel_white"
android:text="@string/menu_cancel_upload"
android:background="@android:color/transparent"
android:layout_margin="8dp"
android:visibility="gone"
/>
<ImageButton
android:id="@+id/retryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_retry_white"
android:text="@string/menu_retry_upload"
android:background="@android:color/transparent"
android:layout_margin="8dp"
android:visibility="gone"
/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View file

@ -25,18 +25,5 @@
android:id="@+id/menu_set_as_wallpaper" android:id="@+id/menu_set_as_wallpaper"
android:title="@string/menu_set_wallpaper" android:title="@string/menu_set_wallpaper"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_retry_current_image"
android:enabled="false"
android:icon="@drawable/ic_undo_white_24dp"
android:title="@string/menu_retry_upload"
android:visible="false"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/menu_cancel_current_image"
android:enabled="false"
android:title="@string/menu_cancel_upload"
android:visible="false"
app:showAsAction="never" />
</menu> </menu>

View file

@ -442,4 +442,7 @@ Upload your first media by touching the camera or gallery icon above.</string>
<string name="display_campaigns_explanation">Tap here to see the ongoing campaigns</string> <string name="display_campaigns_explanation">Tap here to see the ongoing campaigns</string>
<string name="nearby_campaign_dismiss_message">You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish.</string> <string name="nearby_campaign_dismiss_message">You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish.</string>
<string name="this_function_needs_network_connection">This function requires network connection, please check your connection settings.</string>
<string name="bad_token_error_proposed_solution">Upload failed due to issues with edit token. Please try logging out and in again. </string>
</resources> </resources>