use of com.ichi2.anki.CardBrowser.Column.CHANGED in project AnkiChinaAndroid by ankichinateam.
the class SchedV2 method _checkLeech.
/**
* Leeches ****************************************************************** *****************************
*/
/**
* Leech handler. True if card was a leech.
* Overridden: in V1, due and did are changed
*/
protected boolean _checkLeech(@NonNull Card card, @NonNull JSONObject conf) {
int lf;
lf = conf.getInt("leechFails");
if (lf == 0) {
return false;
}
// if over threshold or every half threshold reps after that
if (card.getLapses() >= lf && (card.getLapses() - lf) % Math.max(lf / 2, 1) == 0) {
// add a leech tag
Note n = card.note();
n.addTag("leech");
n.flush();
// handle
if (conf.getInt("leechAction") == Consts.LEECH_SUSPEND) {
card.setQueue(Consts.QUEUE_TYPE_SUSPENDED);
}
// notify UI
if (mContextReference != null) {
Activity context = mContextReference.get();
leech(card, context);
}
return true;
}
return false;
}
use of com.ichi2.anki.CardBrowser.Column.CHANGED in project AnkiChinaAndroid by ankichinateam.
the class MediaSyncer method sync.
public String sync(long restSpace) throws UnknownHttpResponseException, MediaSyncException, NoEnoughServerSpaceException {
// of this class about this difference to the original.
if (mCol.getMedia().needScan()) {
mCon.publishProgress(R.string.sync_media_find);
mCol.log("findChanges");
try {
mCol.getMedia().findChanges();
} catch (SQLException ignored) {
return "corruptMediaDB";
}
}
// begin session and check if in sync
int lastUsn = mCol.getMedia().lastUsn();
JSONObject ret = mServer.begin();
int srvUsn = ret.getInt("usn");
if ((lastUsn == srvUsn) && !(mCol.getMedia().haveDirty())) {
return "noChanges";
}
// loop through and process changes from server
mCol.log("last local usn is " + lastUsn);
mDownloadCount = 0;
while (true) {
// Allow cancellation (note: media sync has no finish command, so just throw)
if (Connection.getIsCancelled()) {
Timber.i("Sync was cancelled");
throw new RuntimeException("UserAbortedSync");
}
JSONArray data = mServer.mediaChanges(lastUsn);
mCol.log("mediaChanges resp count: ", data.length());
if (data.length() == 0) {
break;
}
List<String> need = new ArrayList<>();
lastUsn = data.getJSONArray(data.length() - 1).getInt(1);
for (int i = 0; i < data.length(); i++) {
// Allow cancellation (note: media sync has no finish command, so just throw)
if (Connection.getIsCancelled()) {
Timber.i("Sync was cancelled");
throw new RuntimeException("UserAbortedSync");
}
String fname = data.getJSONArray(i).getString(0);
int rusn = data.getJSONArray(i).getInt(1);
String rsum = null;
if (!data.getJSONArray(i).isNull(2)) {
// If `rsum` is a JSON `null` value, `.optString(2)` will
// return `"null"` as a string
rsum = data.getJSONArray(i).optString(2);
}
Pair<String, Integer> info = mCol.getMedia().syncInfo(fname);
String lsum = info.first;
int ldirty = info.second;
mCol.log(String.format(Locale.US, "check: lsum=%s rsum=%s ldirty=%d rusn=%d fname=%s", TextUtils.isEmpty(lsum) ? "" : lsum.subSequence(0, 4), TextUtils.isEmpty(rsum) ? "" : rsum.subSequence(0, 4), ldirty, rusn, fname));
if (!TextUtils.isEmpty(rsum)) {
// added/changed remotely
if (TextUtils.isEmpty(lsum) || !lsum.equals(rsum)) {
mCol.log("will fetch");
need.add(fname);
} else {
mCol.log("have same already");
}
mCol.getMedia().markClean(Collections.singletonList(fname));
} else if (!TextUtils.isEmpty(lsum)) {
// deleted remotely
if (ldirty == 0) {
mCol.log("delete local");
mCol.getMedia().syncDelete(fname);
} else {
// conflict: local add overrides remote delete
mCol.log("conflict; will send");
}
} else {
// deleted both sides
mCol.log("both sides deleted");
mCol.getMedia().markClean(Collections.singletonList(fname));
}
}
_downloadFiles(need);
mCol.log("update last usn to " + lastUsn);
// commits
mCol.getMedia().setLastUsn(lastUsn);
}
// at this point, we're all up to date with the server's changes,
// and we need to send our own
boolean updateConflict = false;
int toSend = mCol.getMedia().dirtyCount();
long needSize = mCol.getMedia().getMediaSizeNeededUpload();
if (needSize >= 0 && restSpace < needSize && Consts.loginAnkiChina()) {
Timber.d("No Enough Server Space Exception,rest is:" + restSpace + ",need size:" + needSize);
throw new NoEnoughServerSpaceException(restSpace, needSize);
}
while (true) {
Pair<File, List<String>> changesZip = mCol.getMedia().mediaChangesZip();
File zip = changesZip.first;
try {
List<String> fnames = changesZip.second;
if (fnames.size() == 0) {
break;
}
mCon.publishProgress(String.format(AnkiDroidApp.getAppResources().getString(R.string.sync_media_changes_count), toSend));
JSONArray changes = mServer.uploadChanges(zip);
int processedCnt = changes.getInt(0);
int serverLastUsn = changes.getInt(1);
mCol.getMedia().markClean(fnames.subList(0, processedCnt));
mCol.log(String.format(Locale.US, "processed %d, serverUsn %d, clientUsn %d", processedCnt, serverLastUsn, lastUsn));
if (serverLastUsn - processedCnt == lastUsn) {
mCol.log("lastUsn in sync, updating local");
lastUsn = serverLastUsn;
// commits
mCol.getMedia().setLastUsn(serverLastUsn);
} else {
mCol.log("concurrent update, skipping usn update");
// commit for markClean
mCol.getMedia().getDb().commit();
updateConflict = true;
}
toSend -= processedCnt;
} finally {
zip.delete();
}
}
if (updateConflict) {
mCol.log("restart sync due to concurrent update");
return sync(restSpace);
}
int lcnt = mCol.getMedia().mediacount();
String sRet = mServer.mediaSanity(lcnt);
if ("OK".equals(sRet)) {
return "OK";
} else {
mCol.getMedia().forceResync();
return sRet;
}
}
use of com.ichi2.anki.CardBrowser.Column.CHANGED in project AnkiChinaAndroid by ankichinateam.
the class CardTemplateEditorTest method testDeleteTemplate.
@Test
public void testDeleteTemplate() {
String modelName = "Basic (and reversed card)";
// Start the CardTemplateEditor with a specific model, and make sure the model starts unchanged
JSONObject collectionBasicModelOriginal = getCurrentDatabaseModelCopy(modelName);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra("modelId", collectionBasicModelOriginal.getLong("id"));
ActivityController<CardTemplateEditor> templateEditorController = Robolectric.buildActivity(CardTemplateEditor.class, intent).create().start().resume().visible();
saveControllerForCleanup(templateEditorController);
CardTemplateEditor testEditor = (CardTemplateEditor) templateEditorController.get();
Assert.assertFalse("Model should not have changed yet", testEditor.modelHasChanged());
Assert.assertEquals("Model should have 2 templates now", 2, testEditor.getTempModel().getTemplateCount());
// Try to delete the template - click delete, click confirm for card delete, click confirm again for full sync
ShadowActivity shadowTestEditor = shadowOf(testEditor);
Assert.assertTrue("Unable to click?", shadowTestEditor.clickMenuItem(R.id.action_delete));
advanceRobolectricLooper();
Assert.assertEquals("Wrong dialog shown?", "Delete the “Card 1” card type, and its 0 cards?", getDialogText(true));
clickDialogButton(DialogAction.POSITIVE, true);
advanceRobolectricLooper();
Assert.assertTrue("Model should have changed", testEditor.modelHasChanged());
Assert.assertEquals("Model should have 1 template now", 1, testEditor.getTempModel().getTemplateCount());
// Try to delete the template again, but there's only one
Assert.assertTrue("Unable to click?", shadowTestEditor.clickMenuItem(R.id.action_delete));
advanceRobolectricLooper();
Assert.assertEquals("Did not show dialog about deleting only card?", getResourceString(R.string.card_template_editor_cant_delete), getDialogText(true));
Assert.assertEquals("Change already in database?", collectionBasicModelOriginal.toString().trim(), getCurrentDatabaseModelCopy(modelName).toString().trim());
// Save the change to the database and make sure there's only one template after
JSONObject testEditorModelEdited = testEditor.getTempModel().getModel();
Assert.assertTrue("Unable to click?", shadowTestEditor.clickMenuItem(R.id.action_confirm));
advanceRobolectricLooper();
JSONObject collectionBasicModelCopyEdited = getCurrentDatabaseModelCopy(modelName);
Assert.assertNotEquals("model is unchanged?", collectionBasicModelOriginal, collectionBasicModelCopyEdited);
Assert.assertEquals("model did not save?", testEditorModelEdited.toString().trim(), collectionBasicModelCopyEdited.toString().trim());
}
use of com.ichi2.anki.CardBrowser.Column.CHANGED in project AnkiChinaAndroid by ankichinateam.
the class CardTemplateEditorTest method testDeleteTemplateWithSelectivelyGeneratedCards.
/**
* In a model with two card templates using different fields, some notes may only use card 1,
* and some may only use card 2. If you delete the 2nd template,
* it will cause the notes that only use card 2 to disappear.
*
* So the unit test would then be to make a model like the "basic (optional reverse card)"
* with two fields Enable1 and Enable2, and two templates "card 1" and "card 2".
* Both cards use selective generation, so they're empty unless the corresponding field is set.
*
* So then in the unit test you make the model, add the two templates, then you add two notes,
* with Enable1 and Enable2 respectively set to "y".
* Then you try to delete one of the templates and it should fail
*
* (question: but I thought deleting one should work - still one card left to maintain the note,
* and second template delete should fail since we finally get to a place where no cards are left?
* I am having trouble creating selectively generated cards though - I can do one optional field but not 2 ugh)
*/
@Test
public void testDeleteTemplateWithSelectivelyGeneratedCards() {
String modelName = "Basic (optional reversed card)";
Model collectionBasicModelOriginal = getCurrentDatabaseModelCopy(modelName);
// Start the CardTemplateEditor with a specific model, and make sure the model starts unchanged
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra("modelId", collectionBasicModelOriginal.getLong("id"));
ActivityController<CardTemplateEditor> templateEditorController = Robolectric.buildActivity(CardTemplateEditor.class, intent).create().start().resume().visible();
saveControllerForCleanup(templateEditorController);
CardTemplateEditor testEditor = (CardTemplateEditor) templateEditorController.get();
Assert.assertFalse("Model should not have changed yet", testEditor.modelHasChanged());
Assert.assertEquals("Model should have 2 templates now", 2, testEditor.getTempModel().getTemplateCount());
Assert.assertFalse("Ordinal pending add?", TemporaryModel.isOrdinalPendingAdd(testEditor.getTempModel(), 0));
Assert.assertFalse("Ordinal pending add?", TemporaryModel.isOrdinalPendingAdd(testEditor.getTempModel(), 1));
// Try to delete Card 1 template - click delete, check confirm for card delete popup indicating it was possible, then dismiss it
ShadowActivity shadowTestEditor = shadowOf(testEditor);
Assert.assertTrue("Unable to click?", shadowTestEditor.clickMenuItem(R.id.action_delete));
advanceRobolectricLooper();
Assert.assertEquals("Wrong dialog shown?", "Delete the “Card 1” card type, and its 0 cards?", getDialogText(true));
clickDialogButton(DialogAction.NEGATIVE, true);
advanceRobolectricLooper();
Assert.assertFalse("Model should not have changed", testEditor.modelHasChanged());
// Create note with forward and back info, Add Reverse is empty, so should only be one card
Note selectiveGeneratedNote = getCol().newNote(collectionBasicModelOriginal);
selectiveGeneratedNote.setField(0, "TestFront");
selectiveGeneratedNote.setField(1, "TestBack");
String[] fields = selectiveGeneratedNote.getFields();
for (String field : fields) {
Timber.d("Got a field: %s", field);
}
getCol().addNote(selectiveGeneratedNote);
Assert.assertEquals("selective generation should result in one card", 1, getModelCardCount(collectionBasicModelOriginal));
// Try to delete the template again, but there's selective generation means it would orphan the note
Assert.assertTrue("Unable to click?", shadowTestEditor.clickMenuItem(R.id.action_delete));
advanceRobolectricLooper();
Assert.assertEquals("Did not show dialog about deleting only card?", getResourceString(R.string.card_template_editor_would_delete_note), getDialogText(true));
clickDialogButton(DialogAction.POSITIVE, true);
advanceRobolectricLooper();
Assert.assertNull("Can delete used template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 0 }));
Assert.assertEquals("Change already in database?", collectionBasicModelOriginal.toString().trim(), getCurrentDatabaseModelCopy(modelName).toString().trim());
Assert.assertFalse("Ordinal pending add?", TemporaryModel.isOrdinalPendingAdd(testEditor.getTempModel(), 0));
Assert.assertEquals("Change incorrectly added to list?", 0, testEditor.getTempModel().getTemplateChanges().size());
// Assert can delete 'Card 2'
Assert.assertNotNull("Cannot delete unused template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 1 }));
// Edit note to have Add Reverse set to 'y' so we get a second card
selectiveGeneratedNote.setField(2, "y");
selectiveGeneratedNote.flush();
// - assert two cards
Assert.assertEquals("should be two cards now", 2, getModelCardCount(collectionBasicModelOriginal));
// - assert can delete either Card template but not both
Assert.assertNotNull("Cannot delete template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 0 }));
Assert.assertNotNull("Cannot delete template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 1 }));
Assert.assertNull("Can delete both templates?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 0, 1 }));
// A couple more notes to make sure things are okay
Note secondNote = getCol().newNote(collectionBasicModelOriginal);
secondNote.setField(0, "TestFront2");
secondNote.setField(1, "TestBack2");
secondNote.setField(2, "y");
getCol().addNote(secondNote);
// - assert can delete either Card template but not both
Assert.assertNotNull("Cannot delete template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 0 }));
Assert.assertNotNull("Cannot delete template?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 1 }));
Assert.assertNull("Can delete both templates?", getCol().getModels().getCardIdsForModel(collectionBasicModelOriginal.getLong("id"), new int[] { 0, 1 }));
}
use of com.ichi2.anki.CardBrowser.Column.CHANGED in project AnkiChinaAndroid by ankichinateam.
the class CardTest method test_genrem.
@Test
public void test_genrem() {
Collection col = getCol();
Note note = col.newNote();
note.setItem("Front", "1");
note.setItem("Back", "");
col.addNote(note);
assertEquals(1, note.numberOfCards());
Model m = col.getModels().current();
Models mm = col.getModels();
// adding a new template should automatically create cards
JSONObject t = Models.newTemplate("rev");
t.put("qfmt", "{{Front}}");
t.put("afmt", "");
mm.addTemplateModChanged(m, t);
mm.save(m, true);
assertEquals(2, note.numberOfCards());
// if the template is changed to remove cards, they'll be removed
t = m.getJSONArray("tmpls").getJSONObject(1);
t.put("qfmt", "{{Back}}");
mm.save(m, true);
List<Long> rep = col.emptyCids();
col.remCards(rep);
assertEquals(1, note.numberOfCards());
// if we add to the note, a card should be automatically generated
note.load();
note.setItem("Back", "1");
note.flush();
assertEquals(2, note.numberOfCards());
}
Aggregations