use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.
the class CollectionTask method nonTaskUndo.
@VisibleForTesting
public static Card nonTaskUndo(Collection col) {
AbstractSched sched = col.getSched();
Card card = col.undo();
if (card == null) {
/* multi-card action undone, no action to take here */
Timber.d("Multi-select undo succeeded");
} else {
// cid is actually a card id.
// a review was undone,
/* card review undone, set up to review that card again */
Timber.d("Single card review undo succeeded");
card.startTimer();
col.reset();
sched.deferReset(card);
}
return card;
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.
the class Connection method doInBackgroundSync.
/**
* In the payload, success means that the sync did occur correctly and that a change did occur.
* So success can be false without error, if no change occurred at all.
*/
private Payload doInBackgroundSync(Payload data) {
sIsCancellable = true;
Timber.d("doInBackgroundSync()");
// Block execution until any previous background task finishes, or timeout after 5s
boolean ok = TaskManager.waitToFinish(5);
// Unique key allowing to identify the user to AnkiWeb without password
String hkey = (String) data.data[0];
// Whether media should be synced too
boolean media = (Boolean) data.data[1];
// If normal sync can't occur, what to do
ConflictResolution conflictResolution = (ConflictResolution) data.data[2];
// A number AnkiWeb told us to send back. Probably to choose the best server for the user
HostNum hostNum = (HostNum) data.data[3];
// Use safe version that catches exceptions so that full sync is still possible
Collection col = CollectionHelper.getInstance().getColSafe(AnkiDroidApp.getInstance());
boolean colCorruptFullSync = false;
if (!CollectionHelper.getInstance().colIsOpen() || !ok) {
if (FULL_DOWNLOAD == conflictResolution) {
colCorruptFullSync = true;
} else {
return returnGenericError(data);
}
}
try {
CollectionHelper.getInstance().lockCollection();
RemoteServer remoteServer = new RemoteServer(this, hkey, hostNum);
Syncer client = new Syncer(col, remoteServer, hostNum);
// run sync and check state
boolean noChanges = false;
if (conflictResolution == null) {
Timber.i("Sync - starting sync");
publishProgress(R.string.sync_prepare_syncing);
Pair<ConnectionResultType, Object> ret = client.sync(this);
data.message = client.getSyncMsg();
if (ret == null) {
return returnGenericError(data);
}
if (NO_CHANGES != ret.first && SUCCESS != ret.first) {
data.success = false;
data.resultType = ret.first;
data.result = new Object[] { ret.second };
// Check if there was a sanity check error
if (SANITY_CHECK_ERROR == ret.first) {
// Force full sync next time
col.modSchemaNoCheck();
col.save();
}
return data;
}
// save and note success state
if (NO_CHANGES == ret.first) {
// publishProgress(R.string.sync_no_changes_message);
noChanges = true;
}
} else {
try {
// Disable sync cancellation for full-sync
sIsCancellable = false;
FullSyncer fullSyncServer = new FullSyncer(col, hkey, this, hostNum);
switch(conflictResolution) {
case FULL_UPLOAD:
{
Timber.i("Sync - fullsync - upload collection");
publishProgress(R.string.sync_preparing_full_sync_message);
Pair<ConnectionResultType, Object[]> ret = fullSyncServer.upload();
col.reopen();
if (ret == null) {
return returnGenericError(data);
}
if (ret.first == ARBITRARY_STRING && !ret.second[0].equals(HttpSyncer.ANKIWEB_STATUS_OK)) {
data.success = false;
data.resultType = ret.first;
data.result = ret.second;
return data;
}
break;
}
case FULL_DOWNLOAD:
{
Timber.i("Sync - fullsync - download collection");
publishProgress(R.string.sync_downloading_message);
ConnectionResultType ret = fullSyncServer.download();
if (ret == null) {
Timber.w("Sync - fullsync - unknown error");
return returnGenericError(data);
}
if (SUCCESS == ret) {
data.success = true;
col.reopen();
}
if (SUCCESS != ret) {
Timber.w("Sync - fullsync - download failed");
data.success = false;
data.resultType = ret;
if (!colCorruptFullSync) {
col.reopen();
}
return data;
}
break;
}
default:
}
} catch (OutOfMemoryError e) {
Timber.w(e);
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
data.success = false;
data.resultType = OUT_OF_MEMORY_ERROR;
data.result = new Object[0];
return data;
} catch (RuntimeException e) {
Timber.w(e);
if (timeoutOccurred(e)) {
data.resultType = CONNECTION_ERROR;
} else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
data.resultType = USER_ABORTED_SYNC;
} else {
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
data.resultType = IO_EXCEPTION;
}
data.result = new Object[] { e };
data.success = false;
return data;
}
}
// clear undo to avoid non syncing orphans (because undo resets usn too
if (!noChanges) {
col.clearUndo();
}
// then move on to media sync
sIsCancellable = true;
boolean noMediaChanges = false;
String mediaError = null;
if (media) {
RemoteMediaServer mediaServer = new RemoteMediaServer(col, hkey, this, hostNum);
MediaSyncer mediaClient = new MediaSyncer(col, mediaServer, this);
Pair<ConnectionResultType, String> ret;
try {
Timber.i("Sync - Performing media sync");
ret = mediaClient.sync();
if (ret == null || ret.first == null) {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error);
} else {
if (CORRUPT == ret.first) {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_db_error);
noMediaChanges = true;
}
if (NO_CHANGES == ret.first) {
publishProgress(R.string.sync_media_no_changes);
noMediaChanges = true;
}
if (MEDIA_SANITY_FAILED == ret.first) {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_sanity_failed);
} else {
publishProgress(R.string.sync_media_success);
}
}
} catch (RuntimeException e) {
Timber.w(e);
if (timeoutOccurred(e)) {
data.resultType = CONNECTION_ERROR;
data.result = new Object[] { e };
} else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
data.resultType = USER_ABORTED_SYNC;
data.result = new Object[] { e };
}
int downloadedCount = mediaClient.getDownloadCount();
int uploadedCount = mediaClient.getUploadCount();
if (downloadedCount == 0 && uploadedCount == 0) {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error) + "\n\n" + e.getLocalizedMessage();
} else {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_partial_updated, downloadedCount, uploadedCount) + "\n\n" + e.getLocalizedMessage();
}
}
}
if (noChanges && (!media || noMediaChanges)) {
// This means that there is no change at all, neither media nor collection. Not that there was an error.
data.success = false;
data.resultType = NO_CHANGES;
data.result = new Object[0];
} else {
data.success = true;
data.data = new Object[] { conflictResolution, col, mediaError };
}
return data;
} catch (MediaSyncException e) {
Timber.e("Media sync rejected by server");
data.success = false;
data.resultType = MEDIA_SYNC_SERVER_ERROR;
data.result = new Object[] { e };
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
return data;
} catch (UnknownHttpResponseException e) {
Timber.e(e, "doInBackgroundSync -- unknown response code error");
data.success = false;
int code = e.getResponseCode();
String msg = e.getLocalizedMessage();
data.resultType = ERROR;
data.result = new Object[] { code, msg };
return data;
} catch (Exception e) {
// Global error catcher.
// Try to give a human readable error, otherwise print the raw error message
Timber.e(e, "doInBackgroundSync error");
data.success = false;
if (timeoutOccurred(e)) {
data.resultType = CONNECTION_ERROR;
data.result = new Object[] { e };
} else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
data.resultType = USER_ABORTED_SYNC;
data.result = new Object[] { e };
} else {
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
data.resultType = ARBITRARY_STRING;
data.result = new Object[] { e.getLocalizedMessage(), e };
}
return data;
} finally {
Timber.i("Sync Finished - Closing Collection");
// don't bump mod time unless we explicitly save
if (col != null) {
col.close(false);
}
CollectionHelper.getInstance().unlockCollection();
}
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.
the class CardBrowser method onCreateOptionsMenu.
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
Timber.d("onCreateOptionsMenu()");
mActionBarMenu = menu;
if (!mInMultiSelectMode) {
// restore drawer click listener and icon
restoreDrawerIcon();
getMenuInflater().inflate(R.menu.card_browser, menu);
mSaveSearchItem = menu.findItem(R.id.action_save_search);
// the searchview's query always starts empty.
mSaveSearchItem.setVisible(false);
mMySearchesItem = menu.findItem(R.id.action_list_my_searches);
JSONObject savedFiltersObj = getCol().get_config("savedFilters", (JSONObject) null);
mMySearchesItem.setVisible(savedFiltersObj != null && savedFiltersObj.length() > 0);
mSearchItem = menu.findItem(R.id.action_search);
mSearchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// SearchView doesn't support empty queries so we always reset the search when collapsing
mSearchTerms = "";
mSearchView.setQuery(mSearchTerms, false);
searchCards();
// invalidate options menu so that disappeared icons would appear again
supportInvalidateOptionsMenu();
mTempSearchQuery = null;
return true;
}
});
mSearchView = (CardBrowserSearchView) mSearchItem.getActionView();
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextChange(String newText) {
if (mSearchView.shouldIgnoreValueChange()) {
return true;
}
mSaveSearchItem.setVisible(!TextUtils.isEmpty(newText));
mTempSearchQuery = newText;
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
onSearch();
mSearchView.clearFocus();
return true;
}
});
// Fixes #9010 - consistent search after drawer change calls supportInvalidateOptionsMenu (mTempSearchQuery)
if (!TextUtils.isEmpty(mTempSearchQuery) || !TextUtils.isEmpty(mSearchTerms)) {
// This calls mSearchView.setOnSearchClickListener
mSearchItem.expandActionView();
String toUse = !TextUtils.isEmpty(mTempSearchQuery) ? mTempSearchQuery : mSearchTerms;
mSearchView.setQuery(toUse, false);
}
mSearchView.setOnSearchClickListener(v -> {
// Provide SearchView with the previous search terms
mSearchView.setQuery(mSearchTerms, false);
});
} else {
// multi-select mode
getMenuInflater().inflate(R.menu.card_browser_multiselect, menu);
showBackIcon();
}
if (mActionBarMenu != null && mActionBarMenu.findItem(R.id.action_undo) != null) {
MenuItem undo = mActionBarMenu.findItem(R.id.action_undo);
undo.setVisible(getCol().undoAvailable());
undo.setTitle(getResources().getString(R.string.studyoptions_congrats_undo, getCol().undoName(getResources())));
}
// Maybe we were called from ACTION_PROCESS_TEXT.
// In that case we already fill in the search.
Intent intent = getIntent();
Compat compat = CompatHelper.getCompat();
if (compat.ACTION_PROCESS_TEXT.equals(intent.getAction())) {
CharSequence search = intent.getCharSequenceExtra(compat.EXTRA_PROCESS_TEXT);
if (search != null && search.length() != 0) {
Timber.i("CardBrowser :: Called with search intent: %s", search.toString());
mSearchView.setQuery(search, true);
intent.setAction(Intent.ACTION_DEFAULT);
}
}
mPreviewItem = menu.findItem(R.id.action_preview);
onSelectionChanged();
updatePreviewMenuItem();
return super.onCreateOptionsMenu(menu);
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.
the class CardBrowser method onOptionsItemSelected.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (getDrawerToggle().onOptionsItemSelected(item)) {
return true;
}
// (when another operation will be performed on the model, it will undo the latest operation)
if (mUndoSnackbar != null && mUndoSnackbar.isShown())
mUndoSnackbar.dismiss();
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
endMultiSelectMode();
return true;
} else if (itemId == R.id.action_add_note_from_card_browser) {
addNoteFromCardBrowser();
return true;
} else if (itemId == R.id.action_save_search) {
String searchTerms = mSearchView.getQuery().toString();
showDialogFragment(CardBrowserMySearchesDialog.newInstance(null, mMySearchesDialogListener, searchTerms, CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_SAVE));
return true;
} else if (itemId == R.id.action_list_my_searches) {
JSONObject savedFiltersObj = getCol().get_config("savedFilters", (JSONObject) null);
HashMap<String, String> savedFilters;
if (savedFiltersObj != null) {
savedFilters = HashUtil.HashMapInit(savedFiltersObj.length());
for (String searchName : savedFiltersObj) {
savedFilters.put(searchName, savedFiltersObj.optString(searchName));
}
} else {
savedFilters = HashUtil.HashMapInit(0);
}
showDialogFragment(CardBrowserMySearchesDialog.newInstance(savedFilters, mMySearchesDialogListener, "", CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_LIST));
return true;
} else if (itemId == R.id.action_sort_by_size) {
showDialogFragment(CardBrowserOrderDialog.newInstance(mOrder, mOrderAsc, mOrderDialogListener));
return true;
} else if (itemId == R.id.action_show_marked) {
mSearchTerms = "tag:marked";
mSearchView.setQuery("", false);
mSearchView.setQueryHint(getResources().getString(R.string.card_browser_show_marked));
searchCards();
return true;
} else if (itemId == R.id.action_show_suspended) {
mSearchTerms = "is:suspended";
mSearchView.setQuery("", false);
mSearchView.setQueryHint(getResources().getString(R.string.card_browser_show_suspended));
searchCards();
return true;
} else if (itemId == R.id.action_search_by_tag) {
showFilterByTagsDialog();
return true;
} else if (itemId == R.id.action_flag_zero) {
flagTask(0);
return true;
} else if (itemId == R.id.action_flag_one) {
flagTask(1);
return true;
} else if (itemId == R.id.action_flag_two) {
flagTask(2);
return true;
} else if (itemId == R.id.action_flag_three) {
flagTask(3);
return true;
} else if (itemId == R.id.action_flag_four) {
flagTask(4);
return true;
} else if (itemId == R.id.action_flag_five) {
flagTask(5);
return true;
} else if (itemId == R.id.action_flag_six) {
flagTask(6);
return true;
} else if (itemId == R.id.action_flag_seven) {
flagTask(7);
return true;
} else if (itemId == R.id.action_select_flag_zero) {
selectionWithFlagTask(0);
return true;
} else if (itemId == R.id.action_select_flag_one) {
selectionWithFlagTask(1);
return true;
} else if (itemId == R.id.action_select_flag_two) {
selectionWithFlagTask(2);
return true;
} else if (itemId == R.id.action_select_flag_three) {
selectionWithFlagTask(3);
return true;
} else if (itemId == R.id.action_select_flag_four) {
selectionWithFlagTask(4);
return true;
} else if (itemId == R.id.action_select_flag_five) {
selectionWithFlagTask(5);
return true;
} else if (itemId == R.id.action_select_flag_six) {
selectionWithFlagTask(6);
return true;
} else if (itemId == R.id.action_select_flag_seven) {
selectionWithFlagTask(7);
return true;
} else if (itemId == R.id.action_delete_card) {
deleteSelectedNote();
return true;
} else if (itemId == R.id.action_mark_card) {
toggleMark();
return true;
} else if (itemId == R.id.action_suspend_card) {
TaskManager.launchCollectionTask(new CollectionTask.SuspendCardMulti(getSelectedCardIds()), suspendCardHandler());
return true;
} else if (itemId == R.id.action_change_deck) {
showChangeDeckDialog();
return true;
} else if (itemId == R.id.action_undo) {
Timber.w("CardBrowser:: Undo pressed");
onUndo();
return true;
} else if (itemId == R.id.action_select_none) {
onSelectNone();
return true;
} else if (itemId == R.id.action_select_all) {
onSelectAll();
return true;
} else if (itemId == R.id.action_preview) {
onPreview();
return true;
} else if (itemId == R.id.action_reset_cards_progress) {
Timber.i("NoteEditor:: Reset progress button pressed");
onResetProgress();
return true;
} else if (itemId == R.id.action_reschedule_cards) {
Timber.i("CardBrowser:: Reschedule button pressed");
rescheduleSelectedCards();
return true;
} else if (itemId == R.id.action_reposition_cards) {
Timber.i("CardBrowser:: Reposition button pressed");
// Only new cards may be repositioned
List<Long> cardIds = getSelectedCardIds();
for (long cardId : cardIds) {
if (getCol().getCard(cardId).getQueue() != Consts.QUEUE_TYPE_NEW) {
SimpleMessageDialog dialog = SimpleMessageDialog.newInstance(getString(R.string.vague_error), getString(R.string.reposition_card_not_new_error), false);
showDialogFragment(dialog);
return false;
}
}
IntegerDialog repositionDialog = new IntegerDialog();
repositionDialog.setArgs(getString(R.string.reposition_card_dialog_title), getString(R.string.reposition_card_dialog_message), 5);
repositionDialog.setCallbackRunnable(position -> repositionCardsNoValidation(cardIds, position));
showDialogFragment(repositionDialog);
return true;
} else if (itemId == R.id.action_edit_note) {
openNoteEditorForCurrentlySelectedNote();
return super.onOptionsItemSelected(item);
} else if (itemId == R.id.action_view_card_info) {
List<Long> selectedCardIds = getSelectedCardIds();
if (!selectedCardIds.isEmpty()) {
Intent intent = new Intent(this, CardInfo.class);
intent.putExtra("cardId", selectedCardIds.get(0));
startActivityWithAnimation(intent, FADE);
}
return true;
} else if (itemId == R.id.action_edit_tags) {
showEditTagsDialog();
}
return super.onOptionsItemSelected(item);
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.
the class ReviewerTest method testLrnQueueAfterUndo.
@Test
public void testLrnQueueAfterUndo() {
Collection col = getCol();
JSONObject nw = col.getDecks().confForDid(1).getJSONObject("new");
MockTime time = (MockTime) col.getTime();
nw.put("delays", new JSONArray(new int[] { 1, 10, 60, 120 }));
Card[] cards = new Card[4];
cards[0] = addRevNoteUsingBasicModelDueToday("1", "bar").firstCard();
cards[1] = addNoteUsingBasicModel("2", "bar").firstCard();
cards[2] = addNoteUsingBasicModel("3", "bar").firstCard();
waitForAsyncTasksToComplete();
Reviewer reviewer = startReviewer();
waitForAsyncTasksToComplete();
equalFirstField(cards[0], reviewer.mCurrentCard);
reviewer.answerCard(Consts.BUTTON_ONE);
waitForAsyncTasksToComplete();
equalFirstField(cards[1], reviewer.mCurrentCard);
reviewer.answerCard(Consts.BUTTON_ONE);
waitForAsyncTasksToComplete();
undo(reviewer);
waitForAsyncTasksToComplete();
equalFirstField(cards[1], reviewer.mCurrentCard);
reviewer.answerCard(getCol().getSched().getGoodNewButton());
waitForAsyncTasksToComplete();
equalFirstField(cards[2], reviewer.mCurrentCard);
time.addM(2);
reviewer.answerCard(getCol().getSched().getGoodNewButton());
advanceRobolectricLooperWithSleep();
// This failed in #6898 because this card was not in the queue
equalFirstField(cards[0], reviewer.mCurrentCard);
}
Aggregations