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;
}
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;
}
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);
}
}
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);
}
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;
}
}
Aggregations