0
0
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:
Daniel Svärd 2009-10-18 20:45:46 +02:00
parent 0495113b91
commit 88f34fe976
9 changed files with 886 additions and 31 deletions

View File

@ -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;

View 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.
}
}

View 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;
}
}

View File

@ -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;
}

View 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();
}
}
}

View 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;
}
}

View 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;
}
}
}

View File

@ -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

View 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;
}
}