Search in sources :

Example 16 with DUE

use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.

the class AdvancedStatistics method calculateDueAsMetaInfo.

/**
 * Determine forecast statistics based on a computation or simulation of future reviews.
 * Returns all information required by stats.java to plot the 'forecast' chart based on these statistics.
 * The chart will display:
 * - The forecasted number of reviews per review type (relearn, mature, young, learn) as bars
 * - The forecasted number of cards in each state (new, young, mature) as lines
 * @param metaInfo Object which will be filled with all information required by stats.java to plot the 'forecast' chart and returned by this method.
 * @param type Type of 'forecast' chart for which to determine forecast statistics. Accepted values:
 *             Stats.TYPE_MONTH: Determine forecast statistics for next 30 days with 1-day chunks
 *             Stats.TYPE_YEAR:  Determine forecast statistics for next year with 7-day chunks
 *             Stats.TYPE_LIFE:  Determine forecast statistics for next 2 years with 30-day chunks
 * @param context Contains The collection which contains the decks to be simulated.
 *             Also used for access to the database and access to the creation time of the collection.
 *             The creation time of the collection is needed since due times of cards are relative to the creation time of the collection.
 *             So we could pass mCol here.
 * @param dids Deck id's
 * @return @see #metaInfo
 */
public StatsMetaInfo calculateDueAsMetaInfo(StatsMetaInfo metaInfo, Stats.AxisType type, Context context, String dids) {
    if (!AnkiDroidApp.getSharedPrefs(context).getBoolean("advanced_statistics_enabled", false)) {
        return metaInfo;
    }
    // To indicate that we calculated the statistics so that Stats.java knows that it shouldn't display the standard Forecast chart.
    Settings = new Settings(context);
    metaInfo.setStatsCalculated(true);
    Collection mCol = CollectionHelper.getInstance().getCol(context);
    double[][] mSeriesList;
    int[] mValueLabels;
    int[] mColors;
    int[] mAxisTitles;
    int mMaxCards = 0;
    int mMaxElements;
    double mFirstElement;
    double mLastElement = 0;
    int mZeroIndex = 0;
    double[][] mCumulative;
    double mMcount;
    mValueLabels = new int[] { R.string.statistics_relearn, R.string.statistics_mature, R.string.statistics_young, R.string.statistics_learn };
    mColors = new int[] { R.attr.stats_relearn, R.attr.stats_mature, R.attr.stats_young, R.attr.stats_learn };
    mAxisTitles = new int[] { type.ordinal(), R.string.stats_cards, R.string.stats_cumulative_cards };
    PlottableSimulationResult simuationResult = calculateDueAsPlottableSimulationResult(type, mCol, dids);
    ArrayList<int[]> dues = simuationResult.getNReviews();
    mSeriesList = new double[REVIEW_TYPE_COUNT_PLUS_1][dues.size()];
    for (int t = 0; t < dues.size(); t++) {
        int[] data = dues.get(t);
        int nReviews = data[REVIEW_TYPE_LEARN_PLUS_1] + data[REVIEW_TYPE_YOUNG_PLUS_1] + data[REVIEW_TYPE_MATURE_PLUS_1] + data[REVIEW_TYPE_RELEARN_PLUS_1];
        if (nReviews > mMaxCards)
            // Y-Axis: Max. value
            mMaxCards = nReviews;
        // In the bar-chart, the bars will be stacked on top of each other.
        // For the i^{th} bar counting from the bottom we therefore have to
        // provide the sum of the heights of the i^{th} bar and all bars below it.
        // X-Axis: Day / Week / Month
        mSeriesList[TIME][t] = data[TIME];
        mSeriesList[REVIEW_TYPE_LEARN_PLUS_1][t] = data[REVIEW_TYPE_LEARN_PLUS_1] + data[REVIEW_TYPE_YOUNG_PLUS_1] + data[REVIEW_TYPE_MATURE_PLUS_1] + // Y-Axis: # Cards
        data[REVIEW_TYPE_RELEARN_PLUS_1];
        mSeriesList[REVIEW_TYPE_YOUNG_PLUS_1][t] = data[REVIEW_TYPE_LEARN_PLUS_1] + data[REVIEW_TYPE_YOUNG_PLUS_1] + // Y-Axis: # Mature cards
        data[REVIEW_TYPE_MATURE_PLUS_1];
        mSeriesList[REVIEW_TYPE_MATURE_PLUS_1][t] = data[REVIEW_TYPE_LEARN_PLUS_1] + // Y-Axis: # Young
        data[REVIEW_TYPE_YOUNG_PLUS_1];
        // Y-Axis: # Learn
        mSeriesList[REVIEW_TYPE_RELEARN_PLUS_1][t] = data[REVIEW_TYPE_LEARN_PLUS_1];
        if (data[TIME] > mLastElement)
            // X-Axis: Max. value (only for TYPE_LIFE)
            mLastElement = data[TIME];
        if (data[TIME] == 0) {
            // Because we retrieve dues in the past and we should not cumulate them
            mZeroIndex = t;
        }
    }
    // # X values
    mMaxElements = dues.size() - 1;
    switch(type) {
        case TYPE_MONTH:
            // X-Axis: Max. value
            mLastElement = 31;
            break;
        case TYPE_YEAR:
            // X-Axis: Max. value
            mLastElement = 52;
            break;
        default:
    }
    // X-Axis: Min. value
    mFirstElement = 0;
    // Day starting at mZeroIndex, Cumulative # cards
    mCumulative = simuationResult.getNInState();
    mMcount = // Y-Axis: Max. cumulative value
    mCumulative[CARD_TYPE_NEW_PLUS_1][mCumulative[CARD_TYPE_NEW_PLUS_1].length - 1] + mCumulative[CARD_TYPE_YOUNG_PLUS_1][mCumulative[CARD_TYPE_YOUNG_PLUS_1].length - 1] + mCumulative[CARD_TYPE_MATURE_PLUS_1][mCumulative[CARD_TYPE_MATURE_PLUS_1].length - 1];
    // some adjustments to not crash the chartbuilding with empty data
    if (mMaxElements == 0) {
        mMaxElements = 10;
    }
    if (mMcount == 0) {
        mMcount = 10;
    }
    if (mFirstElement == mLastElement) {
        mFirstElement = 0;
        mLastElement = 6;
    }
    if (mMaxCards == 0)
        mMaxCards = 10;
    metaInfo.setmDynamicAxis(true);
    metaInfo.setmHasColoredCumulative(true);
    metaInfo.setmType(type);
    metaInfo.setmTitle(R.string.stats_forecast);
    metaInfo.setmBackwards(true);
    metaInfo.setmValueLabels(mValueLabels);
    metaInfo.setmColors(mColors);
    metaInfo.setmAxisTitles(mAxisTitles);
    metaInfo.setmMaxCards(mMaxCards);
    metaInfo.setmMaxElements(mMaxElements);
    metaInfo.setmFirstElement(mFirstElement);
    metaInfo.setmLastElement(mLastElement);
    metaInfo.setmZeroIndex(mZeroIndex);
    metaInfo.setmCumulative(mCumulative);
    metaInfo.setmMcount(mMcount);
    metaInfo.setmSeriesList(mSeriesList);
    metaInfo.setDataAvailable(dues.size() > 0);
    return metaInfo;
}
Also used : Collection(com.ichi2.libanki.Collection)

Example 17 with DUE

use of com.ichi2.anki.CardBrowser.Column.DUE 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;
}
Also used : Note(com.ichi2.libanki.Note) Activity(android.app.Activity)

Example 18 with DUE

use of com.ichi2.anki.CardBrowser.Column.DUE 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)

Example 19 with DUE

use of com.ichi2.anki.CardBrowser.Column.DUE in project AnkiChinaAndroid by ankichinateam.

the class SchedV2 method _moveToDyn.

protected void _moveToDyn(long did, @NonNull List<Long> ids, int start) {
    Deck deck = mCol.getDecks().get(did);
    ArrayList<Object[]> data = new ArrayList<>();
    int u = mCol.usn();
    int due = start;
    for (Long id : ids) {
        data.add(new Object[] { did, due, u, id });
        due += 1;
    }
    String queue = "";
    if (!deck.getBoolean("resched")) {
        queue = ", queue = " + Consts.QUEUE_TYPE_REV + "";
    }
    mCol.getDb().executeMany("UPDATE cards SET odid = did, " + "odue = due, did = ?, due = (case when due <= 0 then due else ? end), usn = ? " + queue + " WHERE id = ?", data);
}
Also used : ArrayList(java.util.ArrayList) Deck(com.ichi2.libanki.Deck)

Example 20 with DUE

use of com.ichi2.anki.CardBrowser.Column.DUE 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;
    }
}
Also used : SQLException(android.database.SQLException) JSONArray(com.ichi2.utils.JSONArray) ArrayList(java.util.ArrayList) JSONObject(com.ichi2.utils.JSONObject) ArrayList(java.util.ArrayList) List(java.util.List) File(java.io.File) ZipFile(java.util.zip.ZipFile) NoEnoughServerSpaceException(com.ichi2.anki.exception.NoEnoughServerSpaceException)

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