mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 20:03:05 +02:00
Start of implementation of desktop Anki's SRS
Mostly a straight port of the classes and methods from the python code. Code should be in place for getting the next scheduled card, but this is not used to get the new card currently in the UI (ie. it is not tested yet).
This commit is contained in:
parent
0495113b91
commit
88f34fe976
@ -47,12 +47,12 @@ public class AnkiDb {
|
||||
* @param query The raw SQL query to use.
|
||||
* @return The integer result of the query.
|
||||
*/
|
||||
static public int queryScalar(String query) throws SQLException {
|
||||
static public long queryScalar(String query) throws SQLException {
|
||||
Cursor cursor = AnkiDb.database.rawQuery(query, null);
|
||||
if (!cursor.moveToFirst())
|
||||
throw new SQLException("No result for query: " + query);
|
||||
|
||||
int scalar = cursor.getInt(0);
|
||||
long scalar = cursor.getLong(0);
|
||||
cursor.close();
|
||||
|
||||
return scalar;
|
||||
|
310
src/com/ichi2/anki/Card.java
Normal file
310
src/com/ichi2/anki/Card.java
Normal file
@ -0,0 +1,310 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
public class Card {
|
||||
|
||||
// TODO: Javadoc.
|
||||
|
||||
// BEGIN SQL table entries
|
||||
long id; // Primary key
|
||||
long factId; // Foreign key facts.id
|
||||
long cardModelId; // Foreign key cardModels.id
|
||||
float created = System.currentTimeMillis() / 1000f;
|
||||
float modified = System.currentTimeMillis() / 1000f;
|
||||
String tags = "";
|
||||
int ordinal;
|
||||
// Cached - changed on fact update
|
||||
String question = "";
|
||||
String answer = "";
|
||||
// Default to 'normal' priority
|
||||
// This is indexed in deck.java as we need to create a reverse index
|
||||
int priority = 2;
|
||||
float interval = 0;
|
||||
float lastInterval = 0;
|
||||
float due = System.currentTimeMillis() / 1000f;
|
||||
float lastDue = 0;
|
||||
float factor = 2.5f;
|
||||
float lastFactor = 2.5f;
|
||||
float firstAnswered = 0;
|
||||
// Stats
|
||||
int reps = 0;
|
||||
int successive = 0;
|
||||
float averageTime = 0;
|
||||
float reviewTime = 0;
|
||||
int youngEase0 = 0;
|
||||
int youngEase1 = 0;
|
||||
int youngEase2 = 0;
|
||||
int youngEase3 = 0;
|
||||
int youngEase4 = 0;
|
||||
int matureEase0 = 0;
|
||||
int matureEase1 = 0;
|
||||
int matureEase2 = 0;
|
||||
int matureEase3 = 0;
|
||||
int matureEase4 = 0;
|
||||
// This duplicates the above data, because there's no way to map imported
|
||||
// data to the above
|
||||
int yesCount = 0;
|
||||
int noCount = 0;
|
||||
float spaceUntil = 0;
|
||||
float relativeDelay = 0;
|
||||
int isDue = 0;
|
||||
int type = 2;
|
||||
float combinedDue = 0;
|
||||
// END SQL table entries
|
||||
|
||||
// BEGIN JOINed variables
|
||||
CardModel cardModel;
|
||||
Fact fact;
|
||||
// END JOINed variables
|
||||
|
||||
float timerStarted;
|
||||
float timerStopped;
|
||||
float fuzz;
|
||||
|
||||
public Card(Fact fact, CardModel cardModel, float created) {
|
||||
tags = "";
|
||||
id = Util.genID();
|
||||
// New cards start as new & due
|
||||
type = 2;
|
||||
isDue = 1;
|
||||
timerStarted = Float.NaN;
|
||||
timerStopped = Float.NaN;
|
||||
modified = System.currentTimeMillis() / 1000f;
|
||||
if (created != Float.NaN) {
|
||||
this.created = created;
|
||||
this.due = created;
|
||||
}
|
||||
else
|
||||
due = modified;
|
||||
combinedDue = due;
|
||||
this.fact = fact;
|
||||
this.cardModel = cardModel;
|
||||
if (cardModel != null) {
|
||||
cardModelId = cardModel.id;
|
||||
ordinal = cardModel.ordinal;
|
||||
HashMap<String, HashMap<Long, String>> d = new HashMap<String, HashMap<Long, String>>();
|
||||
Iterator<FieldModel> iter = fact.model.fieldModels.iterator();
|
||||
while (iter.hasNext()) {
|
||||
FieldModel fm = iter.next();
|
||||
HashMap<Long, String> field = new HashMap<Long, String>();
|
||||
field.put(fm.id, fact.getFieldValue(fm.name));
|
||||
d.put(fm.name, field);
|
||||
}
|
||||
HashMap<String, String> qa = CardModel.formatQA(id, fact.modelId, d, splitTags(), cardModel);
|
||||
question = qa.get("question");
|
||||
answer = qa.get("answer");
|
||||
}
|
||||
}
|
||||
|
||||
public Card(){
|
||||
this(null, null, Float.NaN);
|
||||
}
|
||||
|
||||
public void setModified() {
|
||||
modified = System.currentTimeMillis() / 1000f;
|
||||
}
|
||||
|
||||
public void startTimer() {
|
||||
timerStarted = System.currentTimeMillis() / 1000f;
|
||||
}
|
||||
|
||||
public void stopTimer() {
|
||||
timerStopped = System.currentTimeMillis() / 1000f;
|
||||
}
|
||||
|
||||
public float thinkingTime() {
|
||||
if (timerStopped == Float.NaN)
|
||||
return (System.currentTimeMillis() / 1000f) - timerStarted;
|
||||
else
|
||||
return timerStopped - timerStarted;
|
||||
}
|
||||
|
||||
public float totalTime() {
|
||||
return (System.currentTimeMillis() / 1000f) - timerStarted;
|
||||
}
|
||||
|
||||
public void genFuzz() {
|
||||
Random rand = new Random();
|
||||
fuzz = 0.95f + (0.1f * rand.nextFloat());
|
||||
}
|
||||
|
||||
public String htmlQuestion(String type, boolean align) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String htmlAnswer(boolean align) {
|
||||
return htmlQuestion("answer", align);
|
||||
}
|
||||
|
||||
public void updateStats(int ease, String state) {
|
||||
reps += 1;
|
||||
if (ease > 1)
|
||||
successive += 1;
|
||||
else
|
||||
successive = 0;
|
||||
|
||||
float delay = totalTime();
|
||||
// Ignore any times over 60 seconds
|
||||
if (delay < 60) {
|
||||
reviewTime += delay;
|
||||
if (averageTime != 0)
|
||||
averageTime = (averageTime + delay) / 2f;
|
||||
else
|
||||
averageTime = delay;
|
||||
}
|
||||
// We don't track first answer for cards
|
||||
if (state == "new")
|
||||
state = "young";
|
||||
// Update ease and yes/no count
|
||||
String attr = state + String.format("Ease%d", ease);
|
||||
try {
|
||||
Field f = this.getClass().getDeclaredField(attr);
|
||||
f.setInt(this, f.getInt(this) + 1);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (ease < 2)
|
||||
noCount += 1;
|
||||
else
|
||||
yesCount += 1;
|
||||
if (firstAnswered == 0)
|
||||
firstAnswered = System.currentTimeMillis() / 1000f;
|
||||
setModified();
|
||||
}
|
||||
|
||||
public String[] splitTags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String allTags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasTag(String tag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean fromDB(long id) {
|
||||
Cursor cursor = AnkiDb.database.rawQuery(
|
||||
"SELECT id, factId, cardModelId, created, modified, tags, " +
|
||||
"ordinal, question, answer, priority, interval, lastInterval, " +
|
||||
"due, lastDue, factor, lastFactor, firstAnswered, reps, " +
|
||||
"successive, averageTime, reviewTime, youngEase0, youngEase1, " +
|
||||
"youngEase2, youngEase3, youngEase4, matureEase0, matureEase1, " +
|
||||
"matureEase2, matureEase3, matureEase4, yesCount, noCount, " +
|
||||
"spaceUntil, isDue, type, combinedDue " +
|
||||
"FROM cards " +
|
||||
"WHERE id = " +
|
||||
id,
|
||||
null);
|
||||
if (!cursor.moveToFirst()) {
|
||||
Log.w("anki", "Card.java (fromDB(id)): No result from query.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.id = cursor.getLong(0);
|
||||
this.factId = cursor.getLong(1);
|
||||
this.cardModelId = cursor.getLong(2);
|
||||
this.created = cursor.getFloat(3);
|
||||
this.modified = cursor.getFloat(4);
|
||||
this.tags = cursor.getString(5);
|
||||
this.ordinal = cursor.getInt(6);
|
||||
this.question = cursor.getString(7);
|
||||
this.answer = cursor.getString(8);
|
||||
this.priority = cursor.getInt(9);
|
||||
this.interval = cursor.getFloat(10);
|
||||
this.lastInterval = cursor.getFloat(11);
|
||||
this.due = cursor.getFloat(12);
|
||||
this.lastDue = cursor.getFloat(13);
|
||||
this.factor = cursor.getFloat(14);
|
||||
this.lastFactor = cursor.getFloat(15);
|
||||
this.firstAnswered = cursor.getFloat(16);
|
||||
this.reps = cursor.getInt(17);
|
||||
this.successive = cursor.getInt(18);
|
||||
this.averageTime = cursor.getFloat(19);
|
||||
this.reviewTime = cursor.getFloat(20);
|
||||
this.youngEase0 = cursor.getInt(21);
|
||||
this.youngEase1 = cursor.getInt(22);
|
||||
this.youngEase2 = cursor.getInt(23);
|
||||
this.youngEase3 = cursor.getInt(24);
|
||||
this.youngEase4 = cursor.getInt(25);
|
||||
this.matureEase0 = cursor.getInt(26);
|
||||
this.matureEase1 = cursor.getInt(27);
|
||||
this.matureEase2 = cursor.getInt(28);
|
||||
this.matureEase3 = cursor.getInt(29);
|
||||
this.matureEase4 = cursor.getInt(30);
|
||||
this.yesCount = cursor.getInt(31);
|
||||
this.noCount = cursor.getInt(32);
|
||||
this.spaceUntil = cursor.getFloat(33);
|
||||
this.isDue = cursor.getInt(34);
|
||||
this.type = cursor.getInt(35);
|
||||
this.combinedDue = cursor.getFloat(36);
|
||||
|
||||
cursor.close();
|
||||
|
||||
// TODO: Should also read JOINed entries CardModel and Fact.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void toDB() {
|
||||
if (this.reps == 0)
|
||||
this.type = 2;
|
||||
else if (this.successive != 0)
|
||||
this.type = 1;
|
||||
else
|
||||
this.type = 0;
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("factId", this.factId);
|
||||
values.put("cardModelId", this.cardModelId);
|
||||
values.put("created", this.created);
|
||||
values.put("modified", this.modified);
|
||||
values.put("tags", this.tags);
|
||||
values.put("ordinal", this.ordinal);
|
||||
values.put("question", this.question);
|
||||
values.put("answer", this.answer);
|
||||
values.put("priority", this.priority);
|
||||
values.put("interval", this.interval);
|
||||
values.put("lastInterval", this.lastInterval);
|
||||
values.put("due", this.due);
|
||||
values.put("lastDue", this.lastDue);
|
||||
values.put("factor", this.factor);
|
||||
values.put("lastFactor", this.lastFactor);
|
||||
values.put("firstAnswered", this.firstAnswered);
|
||||
values.put("reps", this.reps);
|
||||
values.put("successive", this.successive);
|
||||
values.put("averageTime", this.averageTime);
|
||||
values.put("reviewTime", this.reviewTime);
|
||||
values.put("youngEase0", this.youngEase0);
|
||||
values.put("youngEase1", this.youngEase1);
|
||||
values.put("youngEase2", this.youngEase2);
|
||||
values.put("youngEase3", this.youngEase3);
|
||||
values.put("youngEase4", this.youngEase4);
|
||||
values.put("matureEase0", this.matureEase0);
|
||||
values.put("matureEase1", this.matureEase1);
|
||||
values.put("matureEase2", this.matureEase2);
|
||||
values.put("matureEase3", this.matureEase3);
|
||||
values.put("matureEase4", this.matureEase4);
|
||||
values.put("yesCount", this.yesCount);
|
||||
values.put("noCount", this.noCount);
|
||||
values.put("spaceUntil", this.spaceUntil);
|
||||
values.put("isDue", this.isDue);
|
||||
values.put("type", this.type);
|
||||
values.put("combinedDue", Math.max(this.spaceUntil, this.due));
|
||||
values.put("realtiveDelay", 0f);
|
||||
AnkiDb.database.update("cards", values, "id = " + this.id, null);
|
||||
|
||||
// TODO: Should also write JOINED entries: CardModel and Fact.
|
||||
}
|
||||
|
||||
}
|
102
src/com/ichi2/anki/CardModel.java
Normal file
102
src/com/ichi2/anki/CardModel.java
Normal file
@ -0,0 +1,102 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CardModel {
|
||||
|
||||
// TODO: Javadoc.
|
||||
// TODO: Methods for reading/writing from/to DB.
|
||||
|
||||
// BEGIN SQL table columns
|
||||
long id; // Primary key
|
||||
int ordinal;
|
||||
long modelId; // Foreign key models.id
|
||||
String name;
|
||||
String description = "";
|
||||
int active = 1;
|
||||
// Formats: question/answer/last (not used)
|
||||
String qformat;
|
||||
String aformat;
|
||||
String lformat;
|
||||
// Question/answer editor format (not used yet)
|
||||
String qedformat;
|
||||
String aedformat;
|
||||
int questionInAnswer = 0;
|
||||
// Display
|
||||
String questionFontFamily = "Arial";
|
||||
int questionFontSize = 20;
|
||||
String questionFontColour = "#000000";
|
||||
int questionAlign = 0;
|
||||
String answerFontFamily = "Arial";
|
||||
int answerFontSize = 20;
|
||||
String answerFontColour = "#000000";
|
||||
int answerAlign = 0;
|
||||
// Not used
|
||||
String lastFontFamily = "Arial";
|
||||
int lastFontSize = 20;
|
||||
// Used as background colour
|
||||
String lastFontColour = "#FFFFFF";
|
||||
String editQuestionFontFamily = "";
|
||||
int editQuestionFontSize = 0;
|
||||
String editAnswerFontFamily = "";
|
||||
int editAnswerFontSize = 0;
|
||||
// Empty answer
|
||||
int allowEmptyAnswer = 1;
|
||||
String typeAnswer = "";
|
||||
// END SQL table entries
|
||||
|
||||
// Backward reference
|
||||
Model model;
|
||||
|
||||
public CardModel(String name, String qformat, String aformat, boolean active) {
|
||||
this.name = name;
|
||||
this.qformat = qformat;
|
||||
this.aformat = aformat;
|
||||
this.active = active ? 1 : 0;
|
||||
this.id = Util.genID();
|
||||
}
|
||||
|
||||
public CardModel() {
|
||||
this("", "q", "a", true);
|
||||
}
|
||||
|
||||
public CardModel copy() {
|
||||
CardModel cardModel = new CardModel(
|
||||
this.name,
|
||||
this.qformat,
|
||||
this.aformat,
|
||||
(this.active == 1) ? true : false);
|
||||
cardModel.ordinal = this.ordinal;
|
||||
cardModel.modelId = this.modelId;
|
||||
cardModel.description = this.description;
|
||||
cardModel.lformat = this.lformat;
|
||||
cardModel.qedformat = this.qedformat;
|
||||
cardModel.aedformat = this.aedformat;
|
||||
cardModel.questionInAnswer = this.questionInAnswer;
|
||||
cardModel.questionFontFamily = this.questionFontFamily;
|
||||
cardModel.questionFontSize = this.questionFontSize;
|
||||
cardModel.questionFontColour = this.questionFontColour;
|
||||
cardModel.questionAlign = this.questionAlign;
|
||||
cardModel.answerFontFamily = this.answerFontFamily;
|
||||
cardModel.answerFontSize = this.answerFontSize;
|
||||
cardModel.answerFontColour = this.answerFontColour;
|
||||
cardModel.answerAlign = this.answerAlign;
|
||||
cardModel.lastFontFamily = this.lastFontFamily;
|
||||
cardModel.lastFontSize = this.lastFontSize;
|
||||
cardModel.lastFontColour = this.lastFontColour;
|
||||
cardModel.editQuestionFontFamily = this.editQuestionFontFamily;
|
||||
cardModel.editQuestionFontSize = this.editQuestionFontSize;
|
||||
cardModel.editAnswerFontFamily = this.editAnswerFontFamily;
|
||||
cardModel.editAnswerFontSize = this.editAnswerFontSize;
|
||||
cardModel.allowEmptyAnswer = this.allowEmptyAnswer;
|
||||
cardModel.typeAnswer = this.typeAnswer;
|
||||
cardModel.model = null;
|
||||
|
||||
return cardModel;
|
||||
}
|
||||
|
||||
public static HashMap<String, String> formatQA(long cid, long mid, HashMap<String, HashMap<Long, String>> fact, String[] tags, CardModel cm) {
|
||||
// TODO: Port this method from models.py.
|
||||
return null;
|
||||
}
|
||||
}
|
@ -43,12 +43,12 @@ public class Deck {
|
||||
private static final float initialFactor = 2.5f;
|
||||
|
||||
// BEGIN: SQL table columns
|
||||
int id;
|
||||
long id;
|
||||
float created;
|
||||
float modified;
|
||||
String description;
|
||||
int version;
|
||||
int currentModelId;
|
||||
long currentModelId;
|
||||
String syncName;
|
||||
float lastSync;
|
||||
// Scheduling
|
||||
@ -94,11 +94,18 @@ public class Deck {
|
||||
int revCardOrder;
|
||||
// END: SQL table columns
|
||||
|
||||
// BEGIN JOINed variables
|
||||
//Model currentModel; // Deck.currentModelId = Model.id
|
||||
//ArrayList<Model> models; // Deck.id = Model.deckId
|
||||
// END JOINed variables
|
||||
|
||||
float averageFactor;
|
||||
int newCardModulus;
|
||||
int newCountToday;
|
||||
|
||||
float lastLoaded;
|
||||
boolean newEarly;
|
||||
boolean reviewEarly;
|
||||
|
||||
private Stats globalStats;
|
||||
private Stats dailyStats;
|
||||
@ -112,8 +119,8 @@ public class Deck {
|
||||
// sessionStartReps = 0;
|
||||
// sessionStartTime = 0;
|
||||
// lastSessionStart = 0;
|
||||
// newEarly = false;
|
||||
// reviewEarly = false;
|
||||
newEarly = false;
|
||||
reviewEarly = false;
|
||||
}
|
||||
|
||||
public static Deck openDeck(String path) throws SQLException {
|
||||
@ -131,12 +138,12 @@ public class Deck {
|
||||
throw new SQLException();
|
||||
cursor.moveToFirst();
|
||||
|
||||
deck.id = cursor.getInt(0);
|
||||
deck.id = cursor.getLong(0);
|
||||
deck.created = cursor.getFloat(1);
|
||||
deck.modified = cursor.getFloat(2);
|
||||
deck.description = cursor.getString(3);
|
||||
deck.version = cursor.getInt(4);
|
||||
deck.currentModelId = cursor.getInt(5);
|
||||
deck.currentModelId = cursor.getLong(5);
|
||||
deck.syncName = cursor.getString(6);
|
||||
deck.lastSync = cursor.getFloat(7);
|
||||
deck.hardIntervalMin = cursor.getFloat(8);
|
||||
@ -192,9 +199,9 @@ public class Deck {
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
int count = cursor.getCount();
|
||||
int [] ids = new int[count];
|
||||
long[] ids = new long[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
ids[i] = cursor.getInt(0);
|
||||
ids[i] = cursor.getLong(0);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
deck.updatePriorities(ids);
|
||||
@ -218,6 +225,15 @@ public class Deck {
|
||||
return this.modified > this.lastLoaded;
|
||||
}
|
||||
|
||||
private void setModified() {
|
||||
modified = System.currentTimeMillis() / 1000f;
|
||||
}
|
||||
|
||||
private void flushMod() {
|
||||
setModified();
|
||||
commitToDB();
|
||||
}
|
||||
|
||||
private void commitToDB() {
|
||||
Log.i("anki", "Saving deck to DB...");
|
||||
ContentValues values = new ContentValues();
|
||||
@ -278,6 +294,163 @@ public class Deck {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Getting the next card
|
||||
***********************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* Return the next card object.
|
||||
* @return The next due card or null if nothing is due.
|
||||
*/
|
||||
public Card getCard() {
|
||||
checkDue();
|
||||
long id = getCardId();
|
||||
return cardFromId(id);
|
||||
}
|
||||
|
||||
private long getCardId() {
|
||||
long id;
|
||||
// Failed card due?
|
||||
if ((delay0 != 0) && (failedNowCount != 0))
|
||||
return AnkiDb.queryScalar("SELECT id FROM failedCards LIMIT 1");
|
||||
// Failed card queue too big?
|
||||
if (failedSoonCount >= failedCardMax)
|
||||
return AnkiDb.queryScalar("SELECT id FROM failedCards LIMIT 1");
|
||||
// Distribute new cards?
|
||||
if (timeForNewCard()) {
|
||||
id = maybeGetNewCard();
|
||||
if (id != 0)
|
||||
return id;
|
||||
}
|
||||
// Card due for review?
|
||||
if (revCount != 0)
|
||||
return getRevCard();
|
||||
// New cards left?
|
||||
id = maybeGetNewCard();
|
||||
if (id != 0)
|
||||
return id;
|
||||
// Review ahead?
|
||||
if (reviewEarly) {
|
||||
id = getCardIdAhead();
|
||||
if (id != 0)
|
||||
return id;
|
||||
else {
|
||||
resetAfterReviewEarly();
|
||||
checkDue();
|
||||
}
|
||||
}
|
||||
// Display failed cards early/last
|
||||
if (showFailedLast()) {
|
||||
try {
|
||||
id = AnkiDb.queryScalar("SELECT id FROM failedCards limit 1");
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long getCardIdAhead() {
|
||||
long id = AnkiDb.queryScalar(
|
||||
"SELECT id " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 1 and " +
|
||||
"isDue = 0 and " +
|
||||
"priority in (1,2,3,4) " +
|
||||
"ORDER BY combinedDue " +
|
||||
"LIMIT 1");
|
||||
return id;
|
||||
}
|
||||
|
||||
/* Get card: helper functions
|
||||
***********************************************************/
|
||||
|
||||
private boolean timeForNewCard() {
|
||||
if (newCardSpacing == NEW_CARDS_LAST)
|
||||
return false;
|
||||
if (newCardSpacing == NEW_CARDS_FIRST)
|
||||
return true;
|
||||
// Force old if there are very high priority cards
|
||||
try {
|
||||
AnkiDb.queryScalar(
|
||||
"SELECT 1 " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 1 and " +
|
||||
"isDue = 1 and " +
|
||||
"priority = 4 " +
|
||||
"LIMIT 1");
|
||||
} catch (Exception e) { // No result from query.
|
||||
if (newCardModulus == 0)
|
||||
return false;
|
||||
else
|
||||
return (dailyStats.reps % newCardModulus) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private long maybeGetNewCard() {
|
||||
if ((newCountToday == 0) && (!newEarly))
|
||||
return 0;
|
||||
return getNewCard();
|
||||
}
|
||||
|
||||
private String newCardTable() {
|
||||
return (new String[]{
|
||||
"acqCardsOld",
|
||||
"acqCardsOld",
|
||||
"acqCardsNew"})[newCardOrder];
|
||||
}
|
||||
|
||||
private String revCardTable() {
|
||||
return (new String[]{
|
||||
"revCardsOld",
|
||||
"revCardsNew",
|
||||
"revCardsDue",
|
||||
"revCardsRandom"})[revCardOrder];
|
||||
}
|
||||
|
||||
private long getNewCard() {
|
||||
long id;
|
||||
try {
|
||||
id = AnkiDb.queryScalar(
|
||||
"SELECT id " +
|
||||
"FROM " +
|
||||
newCardTable() +
|
||||
"LIMIT 1");
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private long getRevCard() {
|
||||
long id;
|
||||
try {
|
||||
id = AnkiDb.queryScalar(
|
||||
"SELECT id " +
|
||||
"FROM " +
|
||||
revCardTable() +
|
||||
"LIMIT 1");
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean showFailedLast() {
|
||||
return (collapseTime != 0f) || (delay0 == 0);
|
||||
}
|
||||
|
||||
private Card cardFromId(long id) {
|
||||
Card card = new Card();
|
||||
if (!card.fromDB(id))
|
||||
return null;
|
||||
card.genFuzz();
|
||||
card.startTimer();
|
||||
return card;
|
||||
}
|
||||
|
||||
/* Queue/cache management
|
||||
***********************************************************/
|
||||
|
||||
@ -287,26 +460,26 @@ public class Deck {
|
||||
checkDue();
|
||||
// Global counts
|
||||
if (full) {
|
||||
cardCount = AnkiDb.queryScalar("SELECT count(id) FROM cards");
|
||||
factCount = AnkiDb.queryScalar("SELECT count(id) FROM facts");
|
||||
cardCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM cards");
|
||||
factCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM facts");
|
||||
}
|
||||
|
||||
// Due counts
|
||||
failedSoonCount = AnkiDb.queryScalar("SELECT count(id) FROM failedCards");
|
||||
failedNowCount = AnkiDb.queryScalar(
|
||||
failedSoonCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM failedCards");
|
||||
failedNowCount = (int) AnkiDb.queryScalar(
|
||||
"SELECT count(id) " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 0 and " +
|
||||
"isDue = 1 and " +
|
||||
"combinedDue <= " +
|
||||
String.format("%f", (float) (System.currentTimeMillis() / 1000f)));
|
||||
revCount = AnkiDb.queryScalar(
|
||||
revCount = (int) AnkiDb.queryScalar(
|
||||
"SELECT count(id) " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 1 and " +
|
||||
"priority in (1,2,3,4) and " +
|
||||
"isDue = 1");
|
||||
newCount = AnkiDb.queryScalar(
|
||||
newCount = (int) AnkiDb.queryScalar(
|
||||
"SELECT count(id) " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 2 and " +
|
||||
@ -335,7 +508,7 @@ public class Deck {
|
||||
((System.currentTimeMillis() / 1000f) + delay0)),
|
||||
null);
|
||||
|
||||
failedNowCount = AnkiDb.queryScalar(
|
||||
failedNowCount = (int) AnkiDb.queryScalar(
|
||||
"SELECT count(id) " +
|
||||
"FROM cards " +
|
||||
"WHERE type = 0 and " +
|
||||
@ -428,18 +601,46 @@ public class Deck {
|
||||
dailyStats = Stats.dailyStats(this);
|
||||
}
|
||||
|
||||
private void resetAfterReviewEarly() {
|
||||
long[] ids = null;
|
||||
Cursor cursor = AnkiDb.database.rawQuery(
|
||||
"SELECT id " +
|
||||
"FROM cards " +
|
||||
"WHERE priority = -1",
|
||||
null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int count = cursor.getCount();
|
||||
ids = new long[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
ids[i] = cursor.getLong(0);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
if (ids != null) {
|
||||
updatePriorities(ids);
|
||||
flushMod();
|
||||
}
|
||||
if (reviewEarly || newEarly) {
|
||||
reviewEarly = false;
|
||||
newEarly = false;
|
||||
checkDue();
|
||||
}
|
||||
}
|
||||
|
||||
/* Priorities
|
||||
***********************************************************/
|
||||
|
||||
private void updatePriorities(int[] cardIds) {
|
||||
private void updatePriorities(long[] cardIds) {
|
||||
updatePriorities(cardIds, null, true);
|
||||
}
|
||||
|
||||
private void updatePriorities(int[] cardIds, String[] suspend, boolean dirty) {
|
||||
private void updatePriorities(long[] cardIds, String[] suspend, boolean dirty) {
|
||||
Log.i("ank", "Updating priorities...");
|
||||
// Any tags to suspend
|
||||
if (suspend != null) {
|
||||
int[] ids = tagIds(suspend);
|
||||
long[] ids = tagIds(suspend);
|
||||
AnkiDb.database.execSQL(
|
||||
"UPDATE tags " +
|
||||
"SET priority = 0 " +
|
||||
@ -465,9 +666,9 @@ public class Deck {
|
||||
throw new SQLException("No result for query: " + query);
|
||||
|
||||
int len = cursor.getCount();
|
||||
int[][] cards = new int[len][2];
|
||||
long[][] cards = new long[len][2];
|
||||
for (int i = 0; i < len; i++) {
|
||||
cards[i][0] = cursor.getInt(0);
|
||||
cards[i][0] = cursor.getLong(0);
|
||||
cards[i][1] = cursor.getInt(1);
|
||||
}
|
||||
cursor.close();
|
||||
@ -482,7 +683,7 @@ public class Deck {
|
||||
if (cards[i][1] == pri)
|
||||
count++;
|
||||
}
|
||||
int[] cs = new int[count];
|
||||
long[] cs = new long[count];
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (cards[i][1] == pri) {
|
||||
@ -576,7 +777,7 @@ public class Deck {
|
||||
* @param ids The array of integers to include in the list.
|
||||
* @return An SQL compatible string in the format (ids[0],ids[1],..).
|
||||
*/
|
||||
private static String ids2str(int[] ids) {
|
||||
private static String ids2str(long[] ids) {
|
||||
String str = "(";
|
||||
int len = ids.length;
|
||||
for (int i = 0; i < len; i++ ) {
|
||||
@ -594,7 +795,7 @@ public class Deck {
|
||||
* @param tags An array of the tags to get IDs for.
|
||||
* @return An array of IDs of the tags.
|
||||
*/
|
||||
private static int[] tagIds(String[] tags) {
|
||||
private static long[] tagIds(String[] tags) {
|
||||
// TODO: Finish porting this method from tags.py.
|
||||
return null;
|
||||
}
|
||||
|
78
src/com/ichi2/anki/Fact.java
Normal file
78
src/com/ichi2/anki/Fact.java
Normal file
@ -0,0 +1,78 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Fact {
|
||||
|
||||
// TODO: Javadoc.
|
||||
// TODO: Finish porting from facts.py.
|
||||
// TODO: Methods to read/write from/to DB.
|
||||
|
||||
long id;
|
||||
long modelId;
|
||||
|
||||
Model model;
|
||||
TreeSet<Field> fields;
|
||||
|
||||
public Fact(Model model) {
|
||||
this.model = model;
|
||||
this.id = Util.genID();
|
||||
if (model != null) {
|
||||
Iterator<FieldModel> iter = model.fieldModels.iterator();
|
||||
while (iter.hasNext()) {
|
||||
this.fields.add(new Field(iter.next()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFieldValue(String fieldModelName) {
|
||||
Iterator<Field> iter = fields.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Field f = iter.next();
|
||||
if (f.fieldModel.name.equals("fieldModelName")) {
|
||||
return f.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class FieldOrdinalComparator implements Comparator<Field> {
|
||||
@Override
|
||||
public int compare(Field object1, Field object2) {
|
||||
return object1.ordinal - object2.ordinal;
|
||||
}
|
||||
}
|
||||
|
||||
public class Field {
|
||||
|
||||
// TODO: Javadoc.
|
||||
// Methods for reading/writing from/to DB.
|
||||
|
||||
// BEGIN SQL table entries
|
||||
long id; // Primary key
|
||||
long factId; // Foreign key facts.id
|
||||
long fieldModelId; // Foreign key fieldModel.id
|
||||
int ordinal;
|
||||
String value;
|
||||
// END SQL table entries
|
||||
|
||||
// BEGIN JOINed entries
|
||||
FieldModel fieldModel;
|
||||
// END JOINed entries
|
||||
|
||||
// Backward reference
|
||||
Fact fact;
|
||||
|
||||
public Field(FieldModel fieldModel) {
|
||||
if (fieldModel != null) {
|
||||
this.fieldModel = fieldModel;
|
||||
this.ordinal = fieldModel.ordinal;
|
||||
}
|
||||
this.value = "";
|
||||
this.id = Util.genID();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
src/com/ichi2/anki/FieldModel.java
Normal file
57
src/com/ichi2/anki/FieldModel.java
Normal file
@ -0,0 +1,57 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
public class FieldModel {
|
||||
|
||||
// BEGIN SQL table entries
|
||||
long id;
|
||||
int ordinal;
|
||||
long modelId;
|
||||
String name = "";
|
||||
String description = "";
|
||||
// Reused as RTL marker
|
||||
String features = "";
|
||||
int required = 1;
|
||||
int unique = 1;
|
||||
int numeric = 0;
|
||||
// Display
|
||||
String quizFontFamily;
|
||||
int quizFontSize;
|
||||
String quizFontColour;
|
||||
String editFontFamily;
|
||||
int editFontSize = 20;
|
||||
// END SQL table entries
|
||||
|
||||
// Backward reference
|
||||
Model model;
|
||||
|
||||
public FieldModel(String name, boolean required, boolean unique) {
|
||||
this.name = name;
|
||||
this.required = required ? 1 : 0;
|
||||
this.unique = unique ? 1 : 0;
|
||||
this.id = Util.genID();
|
||||
}
|
||||
|
||||
public FieldModel() {
|
||||
this("", true, true);
|
||||
}
|
||||
|
||||
public FieldModel copy() {
|
||||
FieldModel fieldModel = new FieldModel(
|
||||
this.name,
|
||||
(this.required == 1) ? true : false,
|
||||
(this.unique == 1) ? true : false);
|
||||
fieldModel.ordinal = this.ordinal;
|
||||
fieldModel.modelId = this.modelId;
|
||||
fieldModel.description = this.description;
|
||||
fieldModel.features = this.features;
|
||||
fieldModel.numeric = this.numeric;
|
||||
fieldModel.quizFontFamily = this.quizFontFamily;
|
||||
fieldModel.quizFontSize = this.quizFontSize;
|
||||
fieldModel.quizFontColour = this.quizFontColour;
|
||||
fieldModel.editFontFamily = this.editFontFamily;
|
||||
fieldModel.editFontSize = this.editFontSize;
|
||||
fieldModel.model = null;
|
||||
|
||||
return fieldModel;
|
||||
}
|
||||
}
|
72
src/com/ichi2/anki/Model.java
Normal file
72
src/com/ichi2/anki/Model.java
Normal file
@ -0,0 +1,72 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Model {
|
||||
|
||||
// TODO: Javadoc.
|
||||
// TODO: Methods for reading/writing from/to DB.
|
||||
|
||||
// BEGIN SQL table entries
|
||||
long id; // Primary key
|
||||
long deckId; // Foreign key
|
||||
float created = System.currentTimeMillis() / 1000f;
|
||||
float modified = System.currentTimeMillis() / 1000f;
|
||||
String tags = "";
|
||||
String name;
|
||||
String description = "";
|
||||
String features = ""; // obsolete
|
||||
float spacing = 0.1f;
|
||||
float initialSpacing = 60;
|
||||
int source = 0;
|
||||
// BEGIN SQL table entries
|
||||
|
||||
// BEGIN JOINed entries
|
||||
TreeSet<FieldModel> fieldModels;
|
||||
TreeSet<CardModel> cardModels;
|
||||
// END JOINed entries
|
||||
|
||||
public Model(String name) {
|
||||
this.fieldModels = new TreeSet<FieldModel>(new FieldModelOrdinalComparator());
|
||||
this.cardModels = new TreeSet<CardModel>(new CardModelOrdinalComparator());
|
||||
this.name = name;
|
||||
this.id = Util.genID();
|
||||
}
|
||||
|
||||
public Model() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public void setModified() {
|
||||
this.modified = System.currentTimeMillis() / 1000f;
|
||||
}
|
||||
|
||||
public void addFieldModel(FieldModel field) {
|
||||
field.model = this;
|
||||
this.fieldModels.add(field);
|
||||
//this.toDB();
|
||||
}
|
||||
|
||||
public void addCardModel(CardModel card) {
|
||||
card.model = this;
|
||||
this.cardModels.add(card);
|
||||
//this.toDB();
|
||||
}
|
||||
|
||||
|
||||
public static final class FieldModelOrdinalComparator implements Comparator<FieldModel> {
|
||||
@Override
|
||||
public int compare(FieldModel object1, FieldModel object2) {
|
||||
return object1.ordinal - object2.ordinal;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CardModelOrdinalComparator implements Comparator<CardModel> {
|
||||
@Override
|
||||
public int compare(CardModel object1, CardModel object2) {
|
||||
return object1.ordinal - object2.ordinal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ public class Stats {
|
||||
private static final int STATS_DAY = 1;
|
||||
|
||||
// BEGIN: SQL table columns
|
||||
int id;
|
||||
long id;
|
||||
int type;
|
||||
Date day;
|
||||
int reps;
|
||||
@ -61,7 +61,7 @@ public class Stats {
|
||||
matureEase4 = 0;
|
||||
}
|
||||
|
||||
private void fromDB(int id) {
|
||||
private void fromDB(long id) {
|
||||
Log.i("anki", "Reading stats from DB...");
|
||||
Cursor cursor = AnkiDb.database.rawQuery(
|
||||
"SELECT * " +
|
||||
@ -73,7 +73,7 @@ public class Stats {
|
||||
throw new SQLException();
|
||||
cursor.moveToFirst();
|
||||
|
||||
this.id = cursor.getInt(0);
|
||||
this.id = cursor.getLong(0);
|
||||
type = cursor.getInt(1);
|
||||
day = Date.valueOf(cursor.getString(2));
|
||||
reps = cursor.getInt(3);
|
||||
@ -128,7 +128,7 @@ public class Stats {
|
||||
values.put("matureEase2", 0);
|
||||
values.put("matureEase3", 0);
|
||||
values.put("matureEase4", 0);
|
||||
this.id = (int) AnkiDb.database.insert("stats", null, values);
|
||||
this.id = AnkiDb.database.insert("stats", null, values);
|
||||
}
|
||||
|
||||
public static Date genToday(Deck deck) {
|
||||
@ -151,7 +151,7 @@ public class Stats {
|
||||
|
||||
Stats stats = new Stats();
|
||||
if (cursor.moveToFirst()) {
|
||||
stats.fromDB(cursor.getInt(0));
|
||||
stats.fromDB(cursor.getLong(0));
|
||||
cursor.close();
|
||||
return stats;
|
||||
} else
|
||||
@ -180,7 +180,7 @@ public class Stats {
|
||||
|
||||
Stats stats = new Stats();
|
||||
if (cursor.moveToFirst()) {
|
||||
stats.fromDB(cursor.getInt(0));
|
||||
stats.fromDB(cursor.getLong(0));
|
||||
cursor.close();
|
||||
return stats;
|
||||
} else
|
||||
|
35
src/com/ichi2/anki/Util.java
Normal file
35
src/com/ichi2/anki/Util.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.ichi2.anki;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Util {
|
||||
private static TreeSet<Integer> idTree;
|
||||
private static long idTime;
|
||||
|
||||
public static long genID() {
|
||||
long time = System.currentTimeMillis();
|
||||
long id;
|
||||
int rand;
|
||||
Random random = new Random();
|
||||
|
||||
if (idTree == null) {
|
||||
idTree = new TreeSet<Integer>();
|
||||
idTime = time;
|
||||
}
|
||||
else if (idTime != time) {
|
||||
idTime = time;
|
||||
idTree.clear();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
rand = random.nextInt(2^23);
|
||||
if (!idTree.contains(new Integer(rand))) {
|
||||
idTree.add(new Integer(rand));
|
||||
break;
|
||||
}
|
||||
}
|
||||
id = rand << 41 | time;
|
||||
return id;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user