Search in sources :

Example 11 with DUE

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;
}
Also used : Note(com.ichi2.libanki.Note) Activity(android.app.Activity)

Example 12 with DUE

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);
}
Also used : SupportSQLiteDatabase(androidx.sqlite.db.SupportSQLiteDatabase) Random(java.util.Random) JSONObject(com.ichi2.utils.JSONObject) Cursor(android.database.Cursor)

Example 13 with DUE

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);
}
Also used : JSONObject(com.ichi2.utils.JSONObject) ArrayList(java.util.ArrayList) NonNull(androidx.annotation.NonNull)

Example 14 with DUE

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);
            }
        }
    }
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) ConfirmModSchemaException(com.ichi2.anki.exception.ConfirmModSchemaException) ImportExportException(com.ichi2.anki.exception.ImportExportException) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) ConfirmModSchemaException(com.ichi2.anki.exception.ConfirmModSchemaException)

Example 15 with DUE

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();
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) ArrayList(java.util.ArrayList) List(java.util.List) JSONObject(com.ichi2.utils.JSONObject) DeckConfig(com.ichi2.libanki.DeckConfig)

Aggregations

Test (org.junit.Test)58 Collection (com.ichi2.libanki.Collection)57 Card (com.ichi2.libanki.Card)55 Note (com.ichi2.libanki.Note)52 RobolectricTest (com.ichi2.anki.RobolectricTest)51 JSONObject (com.ichi2.utils.JSONObject)25 DeckConfig (com.ichi2.libanki.DeckConfig)20 JSONArray (com.ichi2.utils.JSONArray)16 ArrayList (java.util.ArrayList)16 Cursor (android.database.Cursor)14 Deck (com.ichi2.libanki.Deck)10 ContentResolver (android.content.ContentResolver)6 ContentValues (android.content.ContentValues)6 Uri (android.net.Uri)6 IOException (java.io.IOException)6 HashMap (java.util.HashMap)6 SuppressLint (android.annotation.SuppressLint)5 FileNotFoundException (java.io.FileNotFoundException)5 Activity (android.app.Activity)4 WebView (android.webkit.WebView)4