use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.
the class Sched method _checkLeech.
/**
* Leeches ****************************************************************** *****************************
*/
/**
* Leech handler. True if card was a leech.
*/
@Override
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) {
// if it has an old due, remove it from cram/relearning
if (card.getODue() != 0) {
card.setDue(card.getODue());
}
if (card.getODid() != 0) {
card.setDid(card.getODid());
}
card.setODue(0);
card.setODid(0);
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.DUE in project AnkiChinaAndroid by ankichinateam.
the class Sched method _fillRev.
@Override
protected boolean _fillRev(boolean allowSibling) {
if (!mRevQueue.isEmpty()) {
return true;
}
if (mRevCount == 0) {
return false;
}
SupportSQLiteDatabase db = mCol.getDb().getDatabase();
while (!mRevDids.isEmpty()) {
long did = mRevDids.getFirst();
int lim = Math.min(mQueueLimit, _deckRevLimit(did));
Cursor cur = null;
if (lim != 0) {
mRevQueue.clear();
// fill the queue with the current did
try {
/* Difference with upstream: we take current card into account.
*
* When current card is answered, the card is not due anymore, so does not belong to the queue.
* Furthermore, _burySiblings ensure that the siblings of the current cards are removed from the
* queue to ensure same day spacing. We simulate this action by ensuring that those siblings are not
* filled, except if we know there are cards and we didn't find any non-sibling card. This way, the
* queue is not empty if it should not be empty (important for the conditional belows), but the
* front of the queue contains distinct card.
*/
String idName = (allowSibling) ? "id" : "nid";
long id = (allowSibling) ? currentCardId() : currentCardNid();
cur = db.query("SELECT id FROM cards WHERE did = ? AND queue = " + Consts.QUEUE_TYPE_REV + " AND due <= ?" + " AND " + idName + " != ? LIMIT ?", new Object[] { did, mToday, id, lim });
while (cur.moveToNext()) {
mRevQueue.add(cur.getLong(0));
}
} finally {
if (cur != null && !cur.isClosed()) {
cur.close();
}
}
if (!mRevQueue.isEmpty()) {
// ordering
if (mCol.getDecks().get(did).getInt("dyn") != 0) {
// dynamic decks need due order preserved
// Note: libanki reverses mRevQueue and returns the last element in _getRevCard().
// AnkiDroid differs by leaving the queue intact and returning the *first* element
// in _getRevCard().
} else {
Random r = new Random();
r.setSeed(mToday);
mRevQueue.shuffle(r);
}
// is the current did empty?
if (mRevQueue.size() < lim) {
mRevDids.remove();
}
return true;
}
}
// nothing left in the deck; move to next
mRevDids.remove();
}
// Since we didn't get a card and the count is non-zero, we
// need to check again for any cards that were removed from
// the queue but not buried
_resetRev();
return _fillRev(true);
}
use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.
the class SchedV2 method quickDeckDueTree.
/**
* Similar to deck due tree, but ignore the number of cards.
*
* It may takes a lot of time to compute the number of card, it
* requires multiple database access by deck. Ignoring this number
* lead to the creation of a tree more quickly.
*/
@Override
@NonNull
public List<DeckTreeNode> quickDeckDueTree() {
// Similar to deckDueTree, ignoring the numbers
// Similar to deckDueList
ArrayList<DeckTreeNode> data = new ArrayList<>();
for (JSONObject deck : mCol.getDecks().allSorted()) {
DeckTreeNode g = new DeckTreeNode(mCol, deck.getString("name"), deck.getLong("id"));
data.add(g);
}
return _groupChildren(data, false);
}
use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.
the class Anki2Importer method _importCards.
private void _importCards() {
if (mMustResetLearning) {
try {
mSrc.changeSchedulerVer(2);
} catch (ConfirmModSchemaException e) {
throw new RuntimeException("Changing the scheduler of an import should not cause schema modification", e);
}
}
// build map of guid -> (ord -> cid) and used id cache
mCards = new HashMap<>();
Map<Long, Boolean> existing = new HashMap<>();
Cursor cur = null;
try {
cur = mDst.getDb().getDatabase().query("select f.guid, c.ord, c.id from cards c, notes f " + "where c.nid = f.id", null);
while (cur.moveToNext()) {
String guid = cur.getString(0);
int ord = cur.getInt(1);
long cid = cur.getLong(2);
existing.put(cid, true);
if (mCards.containsKey(guid)) {
mCards.get(guid).put(ord, cid);
} else {
Map<Integer, Long> map = new HashMap<>();
map.put(ord, cid);
mCards.put(guid, map);
}
}
} finally {
if (cur != null) {
cur.close();
}
}
// loop through src
List<Object[]> cards = new ArrayList<>();
int totalCardCount = 0;
final int thresExecCards = 1000;
List<Object[]> revlog = new ArrayList<>();
int totalRevlogCount = 0;
final int thresExecRevlog = 1000;
int usn = mDst.usn();
long aheadBy = mSrc.getSched().getToday() - mDst.getSched().getToday();
try {
mDst.getDb().getDatabase().beginTransaction();
cur = mSrc.getDb().getDatabase().query("select f.guid, f.mid, c.* from cards c, notes f " + "where c.nid = f.id", null);
// Counters for progress updates
int total = cur.getCount();
boolean largeCollection = total > 200;
int onePercent = total / 100;
int i = 0;
while (cur.moveToNext()) {
Object[] card = new Object[] { cur.getString(0), cur.getLong(1), cur.getLong(2), cur.getLong(3), cur.getLong(4), cur.getInt(5), cur.getLong(6), cur.getInt(7), cur.getInt(8), cur.getInt(9), cur.getLong(10), cur.getLong(11), cur.getLong(12), cur.getInt(13), cur.getInt(14), cur.getInt(15), cur.getLong(16), cur.getLong(17), cur.getInt(18), cur.getString(19) };
String guid = (String) card[0];
if (mChangedGuids.containsKey(guid)) {
guid = mChangedGuids.get(guid);
}
if (mIgnoredGuids.containsKey(guid)) {
continue;
}
// does the card's note exist in dst col?
if (!mNotes.containsKey(guid)) {
continue;
}
Object[] dnid = mNotes.get(guid);
// does the card already exist in the dst col?
int ord = (Integer) card[5];
if (mCards.containsKey(guid) && mCards.get(guid).containsKey(ord)) {
// fixme: in future, could update if newer mod time
continue;
}
// doesn't exist. strip off note info, and save src id for later
Object[] oc = card;
card = new Object[oc.length - 2];
System.arraycopy(oc, 2, card, 0, card.length);
long scid = (Long) card[0];
// ensure the card id is unique
while (existing.containsKey(card[0])) {
card[0] = (Long) card[0] + 999;
}
existing.put((Long) card[0], true);
// update cid, nid, etc
card[1] = mNotes.get(guid)[0];
card[2] = _did((Long) card[2]);
if (mTopID < 0) {
mTopID = (long) card[2];
}
card[4] = mCol.getTime().intTime();
card[5] = usn;
// review cards have a due date relative to collection
if ((Integer) card[7] == 2 || (Integer) card[7] == 3 || (Integer) card[6] == 2) {
card[8] = (Long) card[8] - aheadBy;
}
// odue needs updating too
if (((Long) card[14]).longValue() != 0) {
card[14] = (Long) card[14] - aheadBy;
}
// if odid true, convert card from filtered to normal
if ((Long) card[15] != 0) {
// odid
card[15] = 0;
// odue
card[8] = card[14];
card[14] = 0;
// queue
if ((Integer) card[6] == 1) {
// type
card[7] = 0;
} else {
card[7] = card[6];
}
// type
if ((Integer) card[6] == 1) {
card[6] = 0;
}
}
cards.add(card);
// we need to import revlog, rewriting card ids and bumping usn
try (Cursor cur2 = mSrc.getDb().getDatabase().query("select * from revlog where cid = " + scid, null)) {
while (cur2.moveToNext()) {
Object[] rev = new Object[] { cur2.getLong(0), cur2.getLong(1), cur2.getInt(2), cur2.getInt(3), cur2.getLong(4), cur2.getLong(5), cur2.getLong(6), cur2.getLong(7), cur2.getInt(8) };
rev[1] = card[0];
rev[2] = mDst.usn();
revlog.add(rev);
}
}
i++;
// apply card changes partially
if (cards.size() >= thresExecCards) {
totalCardCount += cards.size();
insertCards(cards);
cards.clear();
Timber.d("add cards: %d", totalCardCount);
}
// apply revlog changes partially
if (revlog.size() >= thresExecRevlog) {
totalRevlogCount += revlog.size();
insertRevlog(revlog);
revlog.clear();
Timber.d("add revlog: %d", totalRevlogCount);
}
if (total != 0 && (!largeCollection || i % onePercent == 0)) {
publishProgress(100, i * 100 / total, 0);
}
}
publishProgress(100, 100, 0);
// count total values
totalCardCount += cards.size();
totalRevlogCount += revlog.size();
Timber.d("add cards total: %d", totalCardCount);
Timber.d("add revlog total: %d", totalRevlogCount);
// apply (for last chunk)
insertCards(cards);
cards.clear();
insertRevlog(revlog);
revlog.clear();
mLog.add(getRes().getString(R.string.import_complete_count, totalCardCount));
mDst.getDb().getDatabase().setTransactionSuccessful();
} finally {
if (cur != null) {
cur.close();
}
if (mDst.getDb().getDatabase().inTransaction()) {
try {
mDst.getDb().getDatabase().endTransaction();
} catch (Exception e) {
Timber.w(e);
}
}
}
}
use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.
the class NoteImporter method importNotes.
/**
* Convert each card into a note, apply attributes and add to col.
*/
public void importNotes(List<ForeignNote> notes) {
Assert.that(mappingOk());
// note whether tags are mapped
mTagsMapped = false;
for (String f : mMapping) {
if ("_tags".equals(f)) {
mTagsMapped = true;
break;
}
}
// gather checks for duplicate comparison
HashMap<Long, List<Long>> csums = new HashMap<>();
try (Cursor c = mCol.getDb().query("select csum, id from notes where mid = ?", mModel.getLong("id"))) {
while (c.moveToNext()) {
long csum = c.getLong(0);
long id = c.getLong(1);
if (csums.containsKey(csum)) {
csums.get(csum).add(id);
} else {
csums.put(csum, new ArrayList<>(Collections.singletonList(id)));
}
}
}
HashMap<String, Boolean> firsts = new HashMap<>();
int fld0index = mMapping.indexOf(mModel.getJSONArray("flds").getJSONObject(0).getString("name"));
mFMap = mCol.getModels().fieldMap(mModel);
mNextId = mCol.getTime().timestampID(mCol.getDb(), "notes");
// loop through the notes
List<Object[]> updates = new ArrayList<>();
List<String> updateLog = new ArrayList<>();
// PORT: Translations moved closer to their sources
List<Object[]> _new = new ArrayList<>();
_ids = new ArrayList<>();
_cards = new ArrayList<>();
mEmptyNotes = false;
int dupeCount = 0;
List<String> dupes = new ArrayList<>();
for (ForeignNote n : notes) {
for (int c = 0; c < n.mFields.size(); c++) {
if (!this.mAllowHTML) {
n.mFields.set(c, HtmlUtils.escape(n.mFields.get(c)));
}
n.mFields.set(c, n.mFields.get(c).trim());
if (!this.mAllowHTML) {
n.mFields.set(c, n.mFields.get(c).replace("\n", "<br>"));
}
}
String fld0 = n.mFields.get(fld0index);
long csum = fieldChecksum(fld0);
// first field must exist
if (fld0 == null || fld0.length() == 0) {
getLog().add(getString(R.string.note_importer_error_empty_first_field, TextUtils.join(" ", n.mFields)));
continue;
}
// earlier in import?
if (firsts.containsKey(fld0) && mImportMode != ADD_MODE) {
// duplicates in source file; log and ignore
getLog().add(getString(R.string.note_importer_error_appeared_twice, fld0));
continue;
}
firsts.put(fld0, true);
// already exists?
boolean found = false;
if (csums.containsKey(csum)) {
// csum is not a guarantee; have to check
for (Long id : csums.get(csum)) {
String flds = mCol.getDb().queryString("select flds from notes where id = ?", id);
String[] sflds = splitFields(flds);
if (fld0.equals(sflds[0])) {
// duplicate
found = true;
if (mImportMode == UPDATE_MODE) {
Object[] data = updateData(n, id, sflds);
if (data != null && data.length > 0) {
updates.add(data);
updateLog.add(getString(R.string.note_importer_error_first_field_matched, fld0));
dupeCount += 1;
found = true;
}
} else if (mImportMode == IGNORE_MODE) {
dupeCount += 1;
} else if (mImportMode == ADD_MODE) {
// allow duplicates in this case
if (!dupes.contains(fld0)) {
// only show message once, no matter how many
// duplicates are in the collection already
updateLog.add(getString(R.string.note_importer_error_added_duplicate_first_field, fld0));
dupes.add(fld0);
}
found = false;
}
}
}
}
// newly add
if (!found) {
Object[] data = newData(n);
if (data != null && data.length > 0) {
_new.add(data);
// note that we've seen this note once already
firsts.put(fld0, true);
}
}
}
addNew(_new);
addUpdates(updates);
// make sure to update sflds, etc
mCol.updateFieldCache(collection2Array(_ids));
// generate cards
if (!mCol.genCards(_ids).isEmpty()) {
this.getLog().add(0, getString(R.string.note_importer_empty_cards_found));
}
// apply scheduling updates
updateCards();
// we randomize or order here, to ensure that siblings
// have the same due#
long did = mCol.getDecks().selected();
DeckConfig conf = mCol.getDecks().confForDid(did);
// in order due?
if (conf.getJSONObject("new").getInt("order") == NEW_CARDS_RANDOM) {
mCol.getSched().randomizeCards(did);
}
String part1 = getQuantityString(R.plurals.note_importer_notes_added, _new.size());
String part2 = getQuantityString(R.plurals.note_importer_notes_updated, mUpdateCount);
int unchanged;
if (mImportMode == UPDATE_MODE) {
unchanged = dupeCount - mUpdateCount;
} else if (mImportMode == IGNORE_MODE) {
unchanged = dupeCount;
} else {
unchanged = 0;
}
String part3 = getQuantityString(R.plurals.note_importer_notes_unchanged, unchanged);
mLog.add(String.format("%s, %s, %s.", part1, part2, part3));
mLog.addAll(updateLog);
if (mEmptyNotes) {
mLog.add(getString(R.string.note_importer_error_empty_notes));
}
mTotal = _ids.size();
}
Aggregations