Search in sources :

Example 6 with SECONDS_PER_DAY

use of com.ichi2.libanki.stats.Stats.SECONDS_PER_DAY in project Anki-Android by ankidroid.

the class SchedV2Test method test_learn_dayV2.

@Test
public void test_learn_dayV2() throws Exception {
    Collection col = getColV2();
    // add a note
    Note note = col.newNote();
    note.setItem("Front", "one");
    col.addNote(note);
    col.reset();
    Card c = getCard();
    DeckConfig conf = col.getSched()._cardConf(c);
    conf.getJSONObject("new").put("delays", new JSONArray(new double[] { 1, 10, 1440, 2880 }));
    col.getDecks().save(conf);
    // pass it
    col.getSched().answerCard(c, BUTTON_THREE);
    // two reps to graduate, 1 more today
    assertEquals(3, c.getLeft() % 1000);
    assertEquals(1, c.getLeft() / 1000);
    assertEquals(new Counts(0, 1, 0), col.getSched().counts());
    c = getCard();
    assertEquals(SECONDS_PER_DAY, col.getSched().nextIvl(c, BUTTON_THREE));
    // answering it will place it in queue 3
    col.getSched().answerCard(c, BUTTON_THREE);
    assertEquals(col.getSched().getToday() + 1, c.getDue());
    assertEquals(QUEUE_TYPE_DAY_LEARN_RELEARN, c.getQueue());
    assertNull(getCard());
    // for testing, move it back a day
    c.setDue(c.getDue() - 1);
    c.flush();
    col.reset();
    assertEquals(new Counts(0, 1, 0), col.getSched().counts());
    c = getCard();
    // nextIvl should work
    assertEquals(SECONDS_PER_DAY * 2, col.getSched().nextIvl(c, BUTTON_THREE));
    // if we fail it, it should be back in the correct queue
    col.getSched().answerCard(c, BUTTON_ONE);
    assertEquals(QUEUE_TYPE_LRN, c.getQueue());
    col.undo();
    col.reset();
    c = getCard();
    col.getSched().answerCard(c, BUTTON_THREE);
    // simulate the passing of another two days
    c.setDue(c.getDue() - 2);
    c.flush();
    col.reset();
    // the last pass should graduate it into a review card
    assertEquals(SECONDS_PER_DAY, col.getSched().nextIvl(c, BUTTON_THREE));
    col.getSched().answerCard(c, BUTTON_THREE);
    assertEquals(CARD_TYPE_REV, c.getType());
    assertEquals(QUEUE_TYPE_REV, c.getQueue());
    // if the lapse step is tomorrow, failing it should handle the counts
    // correctly
    c.setDue(0);
    c.flush();
    col.reset();
    assertEquals(new Counts(0, 0, 1), col.getSched().counts());
    conf = col.getSched()._cardConf(c);
    conf.getJSONObject("lapse").put("delays", new JSONArray(new double[] { 1440 }));
    col.getDecks().save(conf);
    c = getCard();
    col.getSched().answerCard(c, BUTTON_ONE);
    assertEquals(QUEUE_TYPE_DAY_LEARN_RELEARN, c.getQueue());
    assertEquals(new Counts(0, 0, 0), col.getSched().counts());
}
Also used : Note(com.ichi2.libanki.Note) JSONArray(com.ichi2.utils.JSONArray) Collection(com.ichi2.libanki.Collection) DeckConfig(com.ichi2.libanki.DeckConfig) Card(com.ichi2.libanki.Card) RobolectricTest(com.ichi2.anki.RobolectricTest) Test(org.junit.Test)

Example 7 with SECONDS_PER_DAY

use of com.ichi2.libanki.stats.Stats.SECONDS_PER_DAY in project Anki-Android by ankidroid.

the class Sched method _updateCutoff.

/**
 * Daily cutoff ************************************************************* **********************************
 * This function uses GregorianCalendar so as to be sensitive to leap years, daylight savings, etc.
 */
@Override
public void _updateCutoff() {
    Integer oldToday = mToday;
    // days since col created
    mToday = (int) ((getTime().intTime() - mCol.getCrt()) / SECONDS_PER_DAY);
    // end of day cutoff
    mDayCutoff = mCol.getCrt() + ((mToday + 1) * SECONDS_PER_DAY);
    if (!mToday.equals(oldToday)) {
        mCol.log(mToday, mDayCutoff);
    }
    // instead
    for (Deck deck : mCol.getDecks().all()) {
        update(deck);
    }
    // unbury if the day has rolled over
    int unburied = mCol.get_config("lastUnburied", 0);
    if (unburied < mToday) {
        SyncStatus.ignoreDatabaseModification(this::unburyCards);
    }
}
Also used : Deck(com.ichi2.libanki.Deck)

Example 8 with SECONDS_PER_DAY

use of com.ichi2.libanki.stats.Stats.SECONDS_PER_DAY in project Anki-Android by ankidroid.

the class FinderTest method test_findCards.

@Test
public void test_findCards() {
    Collection col = getCol();
    Note note = col.newNote();
    note.setItem("Front", "dog");
    note.setItem("Back", "cat");
    note.addTag("monkey animal_1 * %");
    col.addNote(note);
    long n1id = note.getId();
    long firstCardId = note.cards().get(0).getId();
    note = col.newNote();
    note.setItem("Front", "goats are fun");
    note.setItem("Back", "sheep");
    note.addTag("sheep goat horse animal11");
    col.addNote(note);
    long n2id = note.getId();
    note = col.newNote();
    note.setItem("Front", "cat");
    note.setItem("Back", "sheep");
    col.addNote(note);
    Card catCard = note.cards().get(0);
    Model m = col.getModels().current();
    m = col.getModels().copy(m);
    ModelManager mm = col.getModels();
    JSONObject t = Models.newTemplate("Reverse");
    t.put("qfmt", "{{Back}}");
    t.put("afmt", "{{Front}}");
    mm.addTemplateModChanged(m, t);
    mm.save(m);
    note = col.newNote();
    note.setItem("Front", "test");
    note.setItem("Back", "foo bar");
    col.addNote(note);
    col.save();
    List<Long> latestCardIds = note.cids();
    // tag searches
    assertEquals(5, col.findCards("tag:*").size());
    assertEquals(1, col.findCards("tag:\\*").size());
    assertEquals(5, col.findCards("tag:%").size());
    assertEquals(1, col.findCards("tag:\\%").size());
    assertEquals(2, col.findCards("tag:animal_1").size());
    assertEquals(1, col.findCards("tag:animal\\_1").size());
    assertEquals(0, col.findCards("tag:donkey").size());
    assertEquals(1, col.findCards("tag:sheep").size());
    assertEquals(1, col.findCards("tag:sheep tag:goat").size());
    assertEquals(0, col.findCards("tag:sheep tag:monkey").size());
    assertEquals(1, col.findCards("tag:monkey").size());
    assertEquals(1, col.findCards("tag:sheep -tag:monkey").size());
    assertEquals(4, col.findCards("-tag:sheep").size());
    col.getTags().bulkAdd(col.getDb().queryLongList("select id from notes"), "foo bar");
    assertEquals(5, col.findCards("tag:foo").size());
    assertEquals(5, col.findCards("tag:bar").size());
    col.getTags().bulkRem(col.getDb().queryLongList("select id from notes"), "foo");
    assertEquals(0, col.findCards("tag:foo").size());
    assertEquals(5, col.findCards("tag:bar").size());
    // text searches
    assertEquals(2, col.findCards("cat").size());
    assertEquals(1, col.findCards("cat -dog").size());
    assertEquals(1, col.findCards("cat -dog").size());
    assertEquals(1, col.findCards("are goats").size());
    assertEquals(0, col.findCards("\"are goats\"").size());
    assertEquals(1, col.findCards("\"goats are\"").size());
    // card states
    Card c = note.cards().get(0);
    c.setQueue(QUEUE_TYPE_REV);
    c.setType(CARD_TYPE_REV);
    assertEquals(0, col.findCards("is:review").size());
    c.flush();
    assertEqualsArrayList((new Long[] { c.getId() }), col.findCards("is:review"));
    assertEquals(0, col.findCards("is:due").size());
    c.setDue(0);
    c.setQueue(QUEUE_TYPE_REV);
    c.flush();
    assertEqualsArrayList((new Long[] { c.getId() }), col.findCards("is:due"));
    assertEquals(4, col.findCards("-is:due").size());
    c.setQueue(QUEUE_TYPE_SUSPENDED);
    // ensure this card gets a later mod time
    c.flush();
    col.getDb().execute("update cards set mod = mod + 1 where id = ?", c.getId());
    assertEqualsArrayList((new Long[] { c.getId() }), col.findCards("is:suspended"));
    // nids
    assertEquals(0, col.findCards("nid:54321").size());
    assertEquals(2, col.findCards("nid:" + note.getId()).size());
    assertEquals(2, col.findCards("nid:" + n1id + "," + n2id).size());
    // templates
    assertEquals(0, col.findCards("card:foo").size());
    assertEquals(4, col.findCards("\"card:card 1\"").size());
    assertEquals(1, col.findCards("card:reverse").size());
    assertEquals(4, col.findCards("card:1").size());
    assertEquals(1, col.findCards("card:2").size());
    // fields
    assertEquals(1, col.findCards("front:dog").size());
    assertEquals(4, col.findCards("-front:dog").size());
    assertEquals(0, col.findCards("front:sheep").size());
    assertEquals(2, col.findCards("back:sheep").size());
    assertEquals(3, col.findCards("-back:sheep").size());
    assertEquals(0, col.findCards("front:do").size());
    assertEquals(5, col.findCards("front:*").size());
    // ordering
    col.set_config("sortType", "noteCrt");
    col.flush();
    assertTrue(latestCardIds.contains(getLastListElement(col.findCards("front:*", new SortOrder.UseCollectionOrdering()))));
    assertTrue(latestCardIds.contains(getLastListElement(col.findCards("", new SortOrder.UseCollectionOrdering()))));
    col.set_config("sortType", "noteFld");
    col.flush();
    assertEquals(catCard.getId(), (long) col.findCards("", new SortOrder.UseCollectionOrdering()).get(0));
    assertTrue(latestCardIds.contains(getLastListElement(col.findCards("", new SortOrder.UseCollectionOrdering()))));
    col.set_config("sortType", "cardMod");
    col.flush();
    assertTrue(latestCardIds.contains(getLastListElement(col.findCards("", new SortOrder.UseCollectionOrdering()))));
    assertEquals(firstCardId, (long) col.findCards("", new SortOrder.UseCollectionOrdering()).get(0));
    col.set_config("sortBackwards", true);
    col.flush();
    assertTrue(latestCardIds.contains(col.findCards("", new SortOrder.UseCollectionOrdering()).get(0)));
    /* TODO: Port BuiltinSortKind
           assertEquals(firstCardId,
           col.findCards("", BuiltinSortKind.CARD_DUE, reverse=false).get(0)
           );
           assertNotEquals(firstCardId,
           col.findCards("", BuiltinSortKind.CARD_DUE, reverse=true).get(0));
        */
    // model
    assertEquals(3, col.findCards("note:basic").size());
    assertEquals(2, col.findCards("-note:basic").size());
    assertEquals(5, col.findCards("-note:foo").size());
    // col
    assertEquals(5, col.findCards("deck:default").size());
    assertEquals(0, col.findCards("-deck:default").size());
    assertEquals(5, col.findCards("-deck:foo").size());
    assertEquals(5, col.findCards("deck:def*").size());
    assertEquals(5, col.findCards("deck:*EFAULT").size());
    assertEquals(0, col.findCards("deck:*cefault").size());
    // full search
    note = col.newNote();
    note.setItem("Front", "hello<b>world</b>");
    note.setItem("Back", "abc");
    col.addNote(note);
    // as it's the sort field, it matches
    assertEquals(2, col.findCards("helloworld").size());
    // assertEquals(, col.findCards("helloworld", full=true).size())2 This is commented upstream
    // if we put it on the back, it won't
    String note_front = note.getItem("Front");
    String note_back = note.getItem("Back");
    note.setItem("Front", note_back);
    note.setItem("Back", note_front);
    note.flush();
    assertEquals(0, col.findCards("helloworld").size());
    //  Those lines are commented above
    // assertEquals(, col.findCards("helloworld", full=true).size())2
    // assertEquals(, col.findCards("back:helloworld", full=true).size())2
    // searching for an invalid special tag should not error
    // TODO: ensure the search fail
    // assertThrows(Exception.class, () -> col.findCards("is:invalid").size());
    // should be able to limit to parent col, no children
    long id = col.getDb().queryLongScalar("select id from cards limit 1");
    col.getDb().execute("update cards set did = ? where id = ?", addDeck("Default::Child"), id);
    col.save();
    assertEquals(7, col.findCards("deck:default").size());
    assertEquals(1, col.findCards("deck:default::child").size());
    assertEquals(6, col.findCards("deck:default -deck:default::*").size());
    // properties
    id = col.getDb().queryLongScalar("select id from cards limit 1");
    col.getDb().execute("update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 where id = ?", id);
    assertEquals(1, col.findCards("prop:ivl>5").size());
    assertThat(col.findCards("prop:ivl<5").size(), greaterThan(1));
    assertEquals(1, col.findCards("prop:ivl>=5").size());
    assertEquals(0, col.findCards("prop:ivl=9").size());
    assertEquals(1, col.findCards("prop:ivl=10").size());
    assertThat(col.findCards("prop:ivl!=10").size(), greaterThan(1));
    assertEquals(1, col.findCards("prop:due>0").size());
    // due dates should work
    assertEquals(0, col.findCards("prop:due=29").size());
    assertEquals(1, col.findCards("prop:due=30").size());
    // ease factors
    assertEquals(0, col.findCards("prop:ease=2.3").size());
    assertEquals(1, col.findCards("prop:ease=2.2").size());
    assertEquals(1, col.findCards("prop:ease>2").size());
    assertThat(col.findCards("-prop:ease>2").size(), greaterThan(1));
    // recently failed
    if (!isNearCutoff(col)) {
        assertEquals(0, col.findCards("rated:1:1").size());
        assertEquals(0, col.findCards("rated:1:2").size());
        c = getCard();
        col.getSched().answerCard(c, Consts.BUTTON_TWO);
        assertEquals(0, col.findCards("rated:1:1").size());
        assertEquals(1, col.findCards("rated:1:2").size());
        c = getCard();
        col.getSched().answerCard(c, Consts.BUTTON_ONE);
        assertEquals(1, col.findCards("rated:1:1").size());
        assertEquals(1, col.findCards("rated:1:2").size());
        assertEquals(2, col.findCards("rated:1").size());
        assertEquals(0, col.findCards("rated:0:2").size());
        assertEquals(1, col.findCards("rated:2:2").size());
        // added
        assertEquals(0, col.findCards("added:0").size());
        col.getDb().execute("update cards set id = id - " + SECONDS_PER_DAY * 1000 + " where id = ?", id);
        assertEquals(col.cardCount() - 1, col.findCards("added:1").size());
        assertEquals(col.cardCount(), col.findCards("added:2").size());
    } else {
        Timber.w("some find tests disabled near cutoff");
    }
    // empty field
    assertEquals(0, col.findCards("front:").size());
    note = col.newNote();
    note.setItem("Front", "");
    note.setItem("Back", "abc2");
    assertEquals(1, col.addNote(note));
    assertEquals(1, col.findCards("front:").size());
    // OR searches and nesting
    assertEquals(2, col.findCards("tag:monkey or tag:sheep").size());
    assertEquals(2, col.findCards("(tag:monkey OR tag:sheep)").size());
    assertEquals(6, col.findCards("-(tag:monkey OR tag:sheep)").size());
    assertEquals(2, col.findCards("tag:monkey or (tag:sheep sheep)").size());
    assertEquals(1, col.findCards("tag:monkey or (tag:sheep octopus)").size());
// flag
// Todo: ensure it fails
// assertThrows(Exception.class, () -> col.findCards("flag:12"));
}
Also used : JSONObject(com.ichi2.utils.JSONObject) RobolectricTest(com.ichi2.anki.RobolectricTest) Test(org.junit.Test)

Example 9 with SECONDS_PER_DAY

use of com.ichi2.libanki.stats.Stats.SECONDS_PER_DAY in project AnkiChinaAndroid by ankichinateam.

the class Sched method _answerLrnCard.

/**
 * @param ease 1=no, 2=yes, 3=remove
 */
@Override
protected void _answerLrnCard(@NonNull Card card, @Consts.BUTTON_TYPE int ease) {
    JSONObject conf = _lrnConf(card);
    @Consts.CARD_TYPE int type;
    if (card.getODid() != 0 && !card.getWasNew()) {
        type = Consts.CARD_TYPE_RELEARNING;
    } else if (card.getType() == Consts.CARD_TYPE_REV) {
        type = Consts.CARD_TYPE_REV;
    } else {
        type = Consts.CARD_TYPE_NEW;
    }
    boolean leaving = false;
    // lrnCount was decremented once when card was fetched
    int lastLeft = card.getLeft();
    // immediate graduate?
    if (ease == Consts.BUTTON_THREE) {
        _rescheduleAsRev(card, conf, true);
        leaving = true;
    // graduation time?
    } else if (ease == Consts.BUTTON_TWO && (card.getLeft() % 1000) - 1 <= 0) {
        _rescheduleAsRev(card, conf, false);
        leaving = true;
    } else {
        // one step towards graduation
        if (ease == Consts.BUTTON_TWO) {
            // decrement real left count and recalculate left today
            int left = (card.getLeft() % 1000) - 1;
            card.setLeft(_leftToday(conf.getJSONArray("delays"), left) * 1000 + left);
        // failed
        } else {
            card.setLeft(_startingLeft(card));
            boolean resched = _resched(card);
            if (conf.has("mult") && resched) {
                // review that's lapsed
                card.setIvl(Math.max(Math.max(1, (int) (card.getIvl() * conf.getDouble("mult"))), conf.getInt("minInt")));
            } else {
            // new card; no ivl adjustment
            // pass
            }
            if (resched && card.getODid() != 0) {
                card.setODue(mToday + 1);
            }
        }
        int delay = _delayForGrade(conf, card.getLeft());
        if (card.getDue() < getTime().intTime()) {
            // not collapsed; add some randomness
            delay *= Utils.randomFloatInRange(1f, 1.25f);
        }
        card.setDue(getTime().intTime() + delay);
        // due today?
        if (card.getDue() < mDayCutoff) {
            mLrnCount += card.getLeft() / 1000;
            // if the queue is not empty and there's nothing else to do, make
            // sure we don't put it at the head of the queue and end up showing
            // it twice in a row
            card.setQueue(Consts.QUEUE_TYPE_LRN);
            if (!mLrnQueue.isEmpty() && revCount() == 0 && newCount() == 0) {
                long smallestDue = mLrnQueue.getFirstDue();
                card.setDue(Math.max(card.getDue(), smallestDue + 1));
            }
            _sortIntoLrn(card.getDue(), card.getId());
        } else {
            // the card is due in one or more days, so we need to use the day learn queue
            long ahead = ((card.getDue() - mDayCutoff) / SECONDS_PER_DAY) + 1;
            card.setDue(mToday + ahead);
            card.setQueue(Consts.QUEUE_TYPE_DAY_LEARN_RELEARN);
        }
    }
    _logLrn(card, ease, conf, leaving, type, lastLeft);
}
Also used : JSONObject(com.ichi2.utils.JSONObject)

Example 10 with SECONDS_PER_DAY

use of com.ichi2.libanki.stats.Stats.SECONDS_PER_DAY in project AnkiChinaAndroid by ankichinateam.

the class Sched method _rescheduleLapse.

@Override
protected int _rescheduleLapse(@NonNull Card card) {
    JSONObject conf;
    conf = _lapseConf(card);
    card.setLastIvl(card.getIvl());
    if (_resched(card)) {
        card.setLapses(card.getLapses() + 1);
        card.setIvl(_nextLapseIvl(card, conf));
        card.setFactor(Math.max(1300, card.getFactor() - 200));
        card.setDue(mToday + card.getIvl());
        // if it's a filtered deck, update odue as well
        if (card.getODid() != 0) {
            card.setODue(card.getDue());
        }
    }
    // if suspended as a leech, nothing to do
    int delay = 0;
    if (_checkLeech(card, conf) && card.getQueue() == Consts.QUEUE_TYPE_SUSPENDED) {
        return delay;
    }
    // if no relearning steps, nothing to do
    if (conf.getJSONArray("delays").length() == 0) {
        return delay;
    }
    // record rev due date for later
    if (card.getODue() == 0) {
        card.setODue(card.getDue());
    }
    delay = _delayForGrade(conf, 0);
    card.setDue(delay + getTime().intTime());
    card.setLeft(_startingLeft(card));
    // queue 1
    if (card.getDue() < mDayCutoff) {
        mLrnCount += card.getLeft() / 1000;
        card.setQueue(Consts.QUEUE_TYPE_LRN);
        _sortIntoLrn(card.getDue(), card.getId());
    } else {
        // day learn queue
        long ahead = ((card.getDue() - mDayCutoff) / SECONDS_PER_DAY) + 1;
        card.setDue(mToday + ahead);
        card.setQueue(Consts.QUEUE_TYPE_DAY_LEARN_RELEARN);
    }
    return delay;
}
Also used : JSONObject(com.ichi2.utils.JSONObject)

Aggregations

RobolectricTest (com.ichi2.anki.RobolectricTest)14 Test (org.junit.Test)14 Card (com.ichi2.libanki.Card)12 Collection (com.ichi2.libanki.Collection)12 Note (com.ichi2.libanki.Note)12 JSONArray (com.ichi2.utils.JSONArray)10 DeckConfig (com.ichi2.libanki.DeckConfig)8 JSONObject (com.ichi2.utils.JSONObject)6 Deck (com.ichi2.libanki.Deck)4