Search in sources :

Example 1 with Queue

use of com.ichi2.libanki.sched.Counts.Queue in project AnkiChinaAndroid by ankichinateam.

the class StudyOptionsFragment method getCollectionTaskListener.

/**
 * Returns a listener that rebuilds the interface after execute.
 *
 * @param refreshDecklist If true, the listener notifies the parent activity to update its deck list
 *                        to reflect the latest values.
 */
private TaskListener getCollectionTaskListener(final boolean refreshDecklist) {
    return new TaskListener() {

        @Override
        public void onPreExecute() {
        }

        @Override
        public void onPostExecute(TaskData result) {
            dismissProgressDialog();
            if (result != null) {
                // Get the return values back from the AsyncTask
                Object[] obj = result.getObjArray();
                int newCards = (Integer) obj[0];
                int lrnCards = (Integer) obj[1];
                int revCards = (Integer) obj[1] + (Integer) obj[2];
                int totalNew = (Integer) obj[3];
                int totalCards = (Integer) obj[4];
                Timber.i("start refresh list data:" + newCards + "," + lrnCards + "," + revCards + "," + totalNew + "," + totalCards);
                // Don't do anything if the fragment is no longer attached to it's Activity or col has been closed
                if (getActivity() == null) {
                    Timber.e("StudyOptionsFragment.mRefreshFragmentListener :: can't refresh");
                    return;
                }
                // #5506 If we have no view, short circuit all UI logic
                if (mStudyOptionsView == null) {
                    tryOpenCramDeckOptions();
                    return;
                }
                // Reinitialize controls incase changed to filtered deck
                initAllContentViews(mStudyOptionsView);
                // Set the deck name
                String fullName;
                Deck deck = getCol().getDecks().current();
                // Main deck name
                fullName = deck.getString("name");
                String[] name = Decks.path(fullName);
                StringBuilder nameBuilder = new StringBuilder();
                if (name.length > 0) {
                    nameBuilder.append(name[name.length - 1]);
                }
                // if (name.length > 1) {
                // nameBuilder.append("\n").append(name[1]);
                // }
                // if (name.length > 3) {
                // nameBuilder.append("...");
                // }
                // if (name.length > 2) {
                // nameBuilder.append("\n").append(name[name.length - 1]);
                // }
                // mTextDeckName.setText(nameBuilder.toString());
                mDeckListAdapter.mTextDeckName = nameBuilder.toString();
                if (tryOpenCramDeckOptions()) {
                    return;
                }
                // Switch between the empty view, the ordinary view, and the "congratulations" view
                boolean isDynamic = deck.optInt("dyn", 0) != 0;
                if (totalCards == 0 && !isDynamic) {
                    mCurrentContentView = CONTENT_EMPTY;
                    mDeckListAdapter.mDeckInfoLayoutVisible = View.VISIBLE;
                    mDeckListAdapter.mTextCongratsMessageVisible = View.VISIBLE;
                    // mDeckListAdapter.mTextCongratsMessage=getString(R.string.studyoptions_empty);
                    mDeckListAdapter.mButtonStartEnable = false;
                    mDeckListAdapter.mTextButtonStart = getString(R.string.studyoptions_start);
                } else if (newCards + lrnCards + revCards == 0) {
                    mCurrentContentView = CONTENT_CONGRATS;
                    if (!isDynamic) {
                        mDeckListAdapter.mDeckInfoLayoutVisible = View.GONE;
                        mDeckListAdapter.mButtonStartEnable = true;
                        mDeckListAdapter.mTextButtonStart = getString(R.string.add_today_study_amount);
                    } else {
                        mDeckListAdapter.mButtonStartEnable = true;
                        mDeckListAdapter.mTextButtonStart = getString(R.string.add_today_study_amount);
                    }
                    mDeckListAdapter.mTextCongratsMessageVisible = View.VISIBLE;
                // mDeckListAdapter.mTextCongratsMessage=getCol().getSched().finishedMsg(getActivity()).toString();
                // mTextCongratsMessage.setText(getCol().getSched().finishedMsg(getActivity()));
                } else {
                    mCurrentContentView = CONTENT_STUDY_OPTIONS;
                    mDeckListAdapter.mDeckInfoLayoutVisible = View.VISIBLE;
                    mDeckListAdapter.mTextCongratsMessageVisible = View.GONE;
                    mDeckListAdapter.mButtonStartEnable = true;
                    mDeckListAdapter.mTextButtonStart = getString(R.string.studyoptions_start);
                }
                mDeckListAdapter.setButtonStartClickListener(mButtonClickListener);
                mDeckListAdapter.setSelfStudyClickListener(mSelfStudyListener);
                // Set deck description
                String desc;
                if (isDynamic) {
                    desc = getResources().getString(R.string.dyn_deck_desc);
                } else {
                    desc = "";
                // desc = getCol().getDecks().getActualDescription();
                }
                if (desc.length() > 0) {
                    mDeckListAdapter.mTextDeckDescription = desc;
                    mDeckListAdapter.mTextDeckDescriptionVisible = View.VISIBLE;
                // mTextDeckDescription.setText(formatDescription(desc));
                // mTextDeckDescription.setVisibility(View.VISIBLE);
                } else {
                    mDeckListAdapter.mTextDeckDescriptionVisible = View.GONE;
                }
                // Set new/learn/review card counts
                mDeckListAdapter.mTextTodayNew = String.valueOf(newCards);
                mDeckListAdapter.mTextTodayRev = String.valueOf(revCards);
                // Set the total number of new cards in deck
                if (totalNew < NEW_CARD_COUNT_TRUNCATE_THRESHOLD) {
                // if it hasn't been truncated by libanki then just set it usually
                // mTextNewTotal.setText(String.valueOf(totalNew));
                } else {
                    // mTextNewTotal.setText(">1000");
                    if (mFullNewCountThread != null) {
                        // a thread was previously made -- interrupt it
                        mFullNewCountThread.interrupt();
                    }
                // mFullNewCountThread = new Thread(() -> {
                // Collection collection = getCol();
                // TODO: refactor code to not rewrite this query, add to Sched.totalNewForCurrentDeck()
                // String query = "SELECT count(*) FROM cards WHERE did IN " +
                // Utils.ids2str(collection.getDecks().active()) +
                // " AND queue = " + Consts.QUEUE_TYPE_NEW;
                // final int fullNewCount = collection.getDb().queryScalar(query);
                // if (fullNewCount > 0) {
                // Runnable setNewTotalText = new Runnable() {
                // @Override
                // public void run() {
                // mTextNewTotal.setText(String.valueOf(fullNewCount));
                // }
                // };
                // if (!Thread.currentThread().isInterrupted()) {
                // mTextNewTotal.post(setNewTotalText);
                // }
                // }
                // });
                // mFullNewCountThread.start();
                }
                // Set total number of cards
                // mTextTotal.setText(String.valueOf(totalCards));
                double[] data = calculateStat(getCol(), getCol().getDecks().current().optLong("id"));
                mNewCardsNum = (int) data[2];
                mRevCardsNum = revCards;
                mShouldConfigBeforeStudy = mNewCardsNum == totalCards && mShouldConfigBeforeStudy;
                int hardNum = getLapses(getCol(), getCol().getDecks().current().optLong("id"));
                mDeckListAdapter.mTextCountHandled = String.format(Locale.CHINA, "%d", (int) data[0]);
                mDeckListAdapter.mTextCountLearning = String.format(Locale.CHINA, "%d", (int) data[1]);
                mDeckListAdapter.mTextCountNew = String.format(Locale.CHINA, "%d", (int) data[2]);
                mDeckListAdapter.mTextCountHard = String.format(Locale.CHINA, "%d", hardNum);
                mDeckListAdapter.mTextTotal = String.format(Locale.CHINA, "共%d张卡牌", totalCards);
                double percent = 0;
                if (data[2] == 0) {
                    // 新卡已学完,显示已掌握
                    percent = (data[0] + data[1] + data[2] <= 0) ? 0 : (data[0] / (data[0] + data[1] + data[2]) * 100);
                    mDeckListAdapter.mTextHandledNum = String.format(Locale.CHINA, "%.0f/%.0f", data[0], (data[0] + data[1] + data[2]));
                // holder.handled_percent.setText((String.format(Locale.CHINA, "已掌握 %.1f", percent)) + "%");
                } else {
                    percent = (data[0] + data[1] + data[2] <= 0) ? 0 : ((data[0] + data[1]) / (data[0] + data[1] + data[2]) * 100);
                    mDeckListAdapter.mTextHandledNum = String.format(Locale.CHINA, "%.0f/%.0f", data[0] + data[1], data[0] + data[1] + data[2]);
                // holder.handled_percent.setText((String.format(Locale.CHINA, "已学 %.1f", percent)) + "%");
                }
                // double percent = (data[0] + data[1] + data[2] <= 0) ? 0 : (data[0] / (data[0] + data[1] + data[2]) * 100);
                // mStudyProgress.setMax(100*100);
                mDeckListAdapter.mStudyProgress = (int) (percent * 100);
                mDeckListAdapter.mTextHandledPercent = (String.format(Locale.CHINA, data[2] == 0 ? "已掌握 %.1f" : "已学 %.1f", percent)) + "%";
                // Set estimated time remaining
                int eta = (newCards + revCards) * 10 / 60;
                if ((newCards + revCards) % 60 != 0) {
                    eta++;
                }
                if (eta != -1) {
                    mDeckListAdapter.mTextETA = "" + eta;
                } else {
                    mDeckListAdapter.mTextETA = "-";
                }
                mDeckListAdapter.notifyDataSetChangedAll();
                // Rebuild the options menu
                configureToolbar();
            }
            updateDeckList();
            // If in fragmented mode, refresh the deck list
            if (mFragmented && refreshDecklist) {
                mListener.onRequireDeckListUpdate();
            }
        }
    };
}
Also used : Deck(com.ichi2.libanki.Deck) TaskData(com.ichi2.async.TaskData) TaskListener(com.ichi2.async.TaskListener)

Example 2 with Queue

use of com.ichi2.libanki.sched.Counts.Queue 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 3 with Queue

use of com.ichi2.libanki.sched.Counts.Queue in project AnkiChinaAndroid by ankichinateam.

the class SchedV2 method currentCardIsInQueueWithDeck.

protected boolean currentCardIsInQueueWithDeck(@Consts.CARD_QUEUE int queue, long did) {
    // mCurrentCard may be set to null when the reviewer gets closed. So we copy it to be sure to avoid NullPointerException
    Card currentCard = mCurrentCard;
    List<Long> currentCardParentsDid = mCurrentCardParentsDid;
    return currentCard != null && currentCard.getQueue() == queue && currentCardParentsDid != null && currentCardParentsDid.contains(did);
}
Also used : Card(com.ichi2.libanki.Card)

Example 4 with Queue

use of com.ichi2.libanki.sched.Counts.Queue 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 5 with Queue

use of com.ichi2.libanki.sched.Counts.Queue in project AnkiChinaAndroid by ankichinateam.

the class SchedV2 method _burySiblings.

/**
 * Sibling spacing
 * ********************
 */
protected void _burySiblings(@NonNull Card card) {
    ArrayList<Long> toBury = new ArrayList<>();
    JSONObject nconf = _newConf(card);
    boolean buryNew = nconf.optBoolean("bury", true);
    JSONObject rconf = _revConf(card);
    boolean buryRev = rconf.optBoolean("bury", true);
    // loop through and remove from queues
    Cursor cur = null;
    try {
        cur = mCol.getDb().query("select id, queue from cards where nid=? and id!=? " + "and (queue=" + Consts.QUEUE_TYPE_NEW + " or (queue=" + Consts.QUEUE_TYPE_REV + " and due<=?))", card.getNid(), card.getId(), mToday);
        while (cur.moveToNext()) {
            long cid = cur.getLong(0);
            int queue = cur.getInt(1);
            SimpleCardQueue queue_object;
            if (queue == Consts.QUEUE_TYPE_REV) {
                queue_object = mRevQueue;
                if (buryRev) {
                    toBury.add(cid);
                }
            } else {
                queue_object = mNewQueue;
                if (buryNew) {
                    toBury.add(cid);
                }
            }
            // even if burying disabled, we still discard to give
            // same-day spacing
            queue_object.remove(cid);
        }
    } finally {
        if (cur != null && !cur.isClosed()) {
            cur.close();
        }
    }
    // then bury
    if (!toBury.isEmpty()) {
        buryCards(Utils.collection2Array(toBury), false);
    }
}
Also used : JSONObject(com.ichi2.utils.JSONObject) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor)

Aggregations

Card (com.ichi2.libanki.Card)31 Test (org.junit.Test)30 RobolectricTest (com.ichi2.anki.RobolectricTest)29 Collection (com.ichi2.libanki.Collection)27 Note (com.ichi2.libanki.Note)24 JSONObject (com.ichi2.utils.JSONObject)20 JSONArray (com.ichi2.utils.JSONArray)17 DeckConfig (com.ichi2.libanki.DeckConfig)15 Cursor (android.database.Cursor)10 ArrayList (java.util.ArrayList)10 Deck (com.ichi2.libanki.Deck)6 ConfirmModSchemaException (com.ichi2.anki.exception.ConfirmModSchemaException)4 JSONException (com.ichi2.utils.JSONException)3 Nullable (androidx.annotation.Nullable)2 SupportSQLiteDatabase (androidx.sqlite.db.SupportSQLiteDatabase)2 Model (com.ichi2.libanki.Model)2 FileNotFoundException (java.io.FileNotFoundException)2 IOException (java.io.IOException)2 HashMap (java.util.HashMap)2 SuppressLint (android.annotation.SuppressLint)1