use of com.ichi2.libanki.ModelManager in project Anki-Android by ankidroid.
the class SchedV2Test method test_ordcycleV2.
@Test
public void test_ordcycleV2() throws Exception {
Collection col = getColV2();
// add two more templates and set second active
Model m = col.getModels().current();
ModelManager mm = col.getModels();
JSONObject t = Models.newTemplate("Reverse");
t.put("qfmt", "{{Back}}");
t.put("afmt", "{{Front}}");
mm.addTemplateModChanged(m, t);
t = Models.newTemplate("f2");
t.put("qfmt", "{{Front}}");
t.put("afmt", "{{Back}}");
mm.addTemplateModChanged(m, t);
mm.save(m);
// create a new note; it should have 3 cards
Note note = col.newNote();
note.setItem("Front", "1");
note.setItem("Back", "1");
col.addNote(note);
assertEquals(3, col.cardCount());
col.reset();
// ordinals should arrive in order
AbstractSched sched = col.getSched();
Card c = sched.getCard();
advanceRobolectricLooperWithSleep();
// not upstream. But we are not expecting multiple getCard without review
sched.answerCard(c, sched.answerButtons(c) - 1);
assertEquals(0, c.getOrd());
c = sched.getCard();
advanceRobolectricLooperWithSleep();
// not upstream. But we are not expecting multiple getCard without review
sched.answerCard(c, sched.answerButtons(c) - 1);
assertEquals(1, c.getOrd());
c = sched.getCard();
advanceRobolectricLooperWithSleep();
// not upstream. But we are not expecting multiple getCard without review
sched.answerCard(c, sched.answerButtons(c) - 1);
advanceRobolectricLooperWithSleep();
assertEquals(2, c.getOrd());
}
use of com.ichi2.libanki.ModelManager in project Anki-Android by ankidroid.
the class CardContentProvider method query.
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String order) {
if (!hasReadWritePermission() && shouldEnforceQueryOrInsertSecurity()) {
throwSecurityException("query", uri);
}
Collection col = CollectionHelper.getInstance().getCol(mContext);
if (col == null) {
throw new IllegalStateException(COL_NULL_ERROR_MSG);
}
Timber.d(getLogMessage("query", uri));
// Find out what data the user is requesting
int match = sUriMatcher.match(uri);
switch(match) {
case NOTES_V2:
{
/* Search for notes using direct SQL query */
String[] proj = sanitizeNoteProjection(projection);
String sql = SQLiteQueryBuilder.buildQueryString(false, "notes", proj, selection, null, null, order, null);
// Needed for varargs of query
return col.getDb().query(sql, (Object[]) selectionArgs);
}
case NOTES:
{
/* Search for notes using the libanki browser syntax */
String[] proj = sanitizeNoteProjection(projection);
String query = (selection != null) ? selection : "";
List<Long> noteIds = col.findNotes(query);
if ((noteIds != null) && (!noteIds.isEmpty())) {
String sel = String.format("id in (%s)", TextUtils.join(",", noteIds));
String sql = SQLiteQueryBuilder.buildQueryString(false, "notes", proj, sel, null, null, order, null);
return col.getDb().getDatabase().query(sql);
} else {
return null;
}
}
case NOTES_ID:
{
/* Direct access note with specific ID*/
String noteId = uri.getPathSegments().get(1);
String[] proj = sanitizeNoteProjection(projection);
String sql = SQLiteQueryBuilder.buildQueryString(false, "notes", proj, "id=?", null, null, order, null);
return col.getDb().query(sql, noteId);
}
case NOTES_ID_CARDS:
{
Note currentNote = getNoteFromUri(uri, col);
String[] columns = ((projection != null) ? projection : FlashCardsContract.Card.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
for (Card currentCard : currentNote.cards()) {
addCardToCursor(currentCard, rv, col, columns);
}
return rv;
}
case NOTES_ID_CARDS_ORD:
{
Card currentCard = getCardFromUri(uri, col);
String[] columns = ((projection != null) ? projection : FlashCardsContract.Card.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
addCardToCursor(currentCard, rv, col, columns);
return rv;
}
case MODELS:
{
ModelManager models = col.getModels();
String[] columns = ((projection != null) ? projection : FlashCardsContract.Model.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
for (Long modelId : models.getModels().keySet()) {
addModelToCursor(modelId, models, rv, columns);
}
return rv;
}
case MODELS_ID:
{
long modelId = getModelIdFromUri(uri, col);
String[] columns = ((projection != null) ? projection : FlashCardsContract.Model.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
addModelToCursor(modelId, col.getModels(), rv, columns);
return rv;
}
case MODELS_ID_TEMPLATES:
{
/* Direct access model templates */
ModelManager models = col.getModels();
Model currentModel = models.get(getModelIdFromUri(uri, col));
String[] columns = ((projection != null) ? projection : CardTemplate.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
try {
JSONArray templates = currentModel.getJSONArray("tmpls");
for (int idx = 0; idx < templates.length(); idx++) {
JSONObject template = templates.getJSONObject(idx);
addTemplateToCursor(template, currentModel, idx + 1, models, rv, columns);
}
} catch (JSONException e) {
throw new IllegalArgumentException("Model is malformed", e);
}
return rv;
}
case MODELS_ID_TEMPLATES_ID:
{
/* Direct access model template with specific ID */
ModelManager models = col.getModels();
int ord = Integer.parseInt(uri.getLastPathSegment());
Model currentModel = models.get(getModelIdFromUri(uri, col));
String[] columns = ((projection != null) ? projection : CardTemplate.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
try {
JSONObject template = getTemplateFromUri(uri, col);
addTemplateToCursor(template, currentModel, ord + 1, models, rv, columns);
} catch (JSONException e) {
throw new IllegalArgumentException("Model is malformed", e);
}
return rv;
}
case SCHEDULE:
{
String[] columns = ((projection != null) ? projection : FlashCardsContract.ReviewInfo.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
long selectedDeckBeforeQuery = col.getDecks().selected();
long deckIdOfTemporarilySelectedDeck = -1;
// the number of scheduled cards to return
int limit = 1;
int selectionArgIndex = 0;
// parsing the selection arguments
if (selection != null) {
// split selection to get arguments like "limit=?"
String[] args = selection.split(",");
for (String arg : args) {
// split arguments into key ("limit") and value ("?")
String[] keyAndValue = arg.split("=");
try {
// check if value is a placeholder ("?"), if so replace with the next value of selectionArgs
String value = "?".equals(keyAndValue[1].trim()) ? selectionArgs[selectionArgIndex++] : keyAndValue[1];
if ("limit".equals(keyAndValue[0].trim())) {
limit = Integer.parseInt(value);
} else if ("deckID".equals(keyAndValue[0].trim())) {
deckIdOfTemporarilySelectedDeck = Long.parseLong(value);
if (!selectDeckWithCheck(col, deckIdOfTemporarilySelectedDeck)) {
// if the provided deckID is wrong, return empty cursor.
return rv;
}
}
} catch (NumberFormatException nfe) {
Timber.w(nfe);
}
}
}
// retrieve the number of cards provided by the selection parameter "limit"
col.getSched().deferReset();
for (int k = 0; k < limit; k++) {
Card currentCard = col.getSched().getCard();
if (currentCard == null) {
break;
}
int buttonCount = col.getSched().answerButtons(currentCard);
JSONArray buttonTexts = new JSONArray();
for (int i = 0; i < buttonCount; i++) {
buttonTexts.put(col.getSched().nextIvlStr(mContext, currentCard, i + 1));
}
addReviewInfoToCursor(currentCard, buttonTexts, buttonCount, rv, col, columns);
}
if (deckIdOfTemporarilySelectedDeck != -1) {
// if the selected deck was changed
// change the selected deck back to the one it was before the query
col.getDecks().select(selectedDeckBeforeQuery);
}
return rv;
}
case DECKS:
{
List<DeckDueTreeNode> allDecks = col.getSched().deckDueList();
String[] columns = ((projection != null) ? projection : FlashCardsContract.Deck.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, allDecks.size());
for (DeckDueTreeNode deck : allDecks) {
long id = deck.getDid();
String name = deck.getFullDeckName();
addDeckToCursor(id, name, getDeckCountsFromDueTreeNode(deck), rv, col, columns);
}
return rv;
}
case DECKS_ID:
{
/* Direct access deck */
String[] columns = ((projection != null) ? projection : FlashCardsContract.Deck.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
List<DeckDueTreeNode> allDecks = col.getSched().deckDueList();
long deckId = Long.parseLong(uri.getPathSegments().get(1));
for (DeckDueTreeNode deck : allDecks) {
if (deck.getDid() == deckId) {
addDeckToCursor(deckId, deck.getFullDeckName(), getDeckCountsFromDueTreeNode(deck), rv, col, columns);
return rv;
}
}
return rv;
}
case DECK_SELECTED:
{
long id = col.getDecks().selected();
String name = col.getDecks().name(id);
String[] columns = ((projection != null) ? projection : FlashCardsContract.Deck.DEFAULT_PROJECTION);
MatrixCursor rv = new MatrixCursor(columns, 1);
JSONArray counts = new JSONArray(Collections.singletonList(col.getSched().counts()));
addDeckToCursor(id, name, counts, rv, col, columns);
return rv;
}
default:
// Unknown URI type
throw new IllegalArgumentException("uri " + uri + " is not supported");
}
}
use of com.ichi2.libanki.ModelManager in project Anki-Android by ankidroid.
the class CardContentProvider method addModelToCursor.
private void addModelToCursor(Long modelId, ModelManager models, MatrixCursor rv, String[] columns) {
Model jsonObject = models.get(modelId);
MatrixCursor.RowBuilder rb = rv.newRow();
try {
for (String column : columns) {
switch(column) {
case FlashCardsContract.Model._ID:
rb.add(modelId);
break;
case FlashCardsContract.Model.NAME:
rb.add(jsonObject.getString("name"));
break;
case FlashCardsContract.Model.FIELD_NAMES:
JSONArray flds = jsonObject.getJSONArray("flds");
String[] allFlds = new String[flds.length()];
for (int idx = 0; idx < flds.length(); idx++) {
allFlds[idx] = flds.getJSONObject(idx).optString("name", "");
}
rb.add(Utils.joinFields(allFlds));
break;
case FlashCardsContract.Model.NUM_CARDS:
rb.add(jsonObject.getJSONArray("tmpls").length());
break;
case FlashCardsContract.Model.CSS:
rb.add(jsonObject.getString("css"));
break;
case FlashCardsContract.Model.DECK_ID:
// #6378 - Anki Desktop changed schema temporarily to allow null
rb.add(jsonObject.optLong("did", Consts.DEFAULT_DECK_ID));
break;
case FlashCardsContract.Model.SORT_FIELD_INDEX:
rb.add(jsonObject.getLong("sortf"));
break;
case FlashCardsContract.Model.TYPE:
rb.add(jsonObject.getLong("type"));
break;
case FlashCardsContract.Model.LATEX_POST:
rb.add(jsonObject.getString("latexPost"));
break;
case FlashCardsContract.Model.LATEX_PRE:
rb.add(jsonObject.getString("latexPre"));
break;
case FlashCardsContract.Model.NOTE_COUNT:
rb.add(models.useCount(jsonObject));
break;
default:
throw new UnsupportedOperationException("Queue \"" + column + "\" is unknown");
}
}
} catch (JSONException e) {
Timber.e(e, "Error parsing JSONArray");
throw new IllegalArgumentException("Model " + modelId + " is malformed", e);
}
}
use of com.ichi2.libanki.ModelManager in project Anki-Android by ankidroid.
the class DeckPicker method showStartupScreensAndDialogs.
private void showStartupScreensAndDialogs(SharedPreferences preferences, int skip) {
// For Android 8/8.1 we want to use software rendering by default or the Reviewer UI is broken #7369
if (CompatHelper.getSdkVersion() == Build.VERSION_CODES.O || CompatHelper.getSdkVersion() == Build.VERSION_CODES.O_MR1) {
if (!preferences.contains("softwareRender")) {
Timber.i("Android 8/8.1 detected with no render preference. Turning on software render.");
preferences.edit().putBoolean("softwareRender", true).apply();
} else {
Timber.i("Android 8/8.1 detected, software render preference already exists.");
}
}
if (!BackupManager.enoughDiscSpace(CollectionHelper.getCurrentAnkiDroidDirectory(this))) {
Timber.i("Not enough space to do backup");
showDialogFragment(DeckPickerNoSpaceLeftDialog.newInstance());
} else if (preferences.getBoolean("noSpaceLeft", false)) {
Timber.i("No space left");
showDialogFragment(DeckPickerBackupNoSpaceLeftDialog.newInstance());
preferences.edit().remove("noSpaceLeft").apply();
} else if (InitialActivity.performSetupFromFreshInstallOrClearedPreferences(preferences)) {
onFinishedStartup();
} else if (skip < 2 && !InitialActivity.isLatestVersion(preferences)) {
Timber.i("AnkiDroid is being updated and a collection already exists.");
// The user might appreciate us now, see if they will help us get better?
if (!preferences.contains(UsageAnalytics.ANALYTICS_OPTIN_KEY)) {
displayAnalyticsOptInDialog();
}
// For upgrades, we check if we are upgrading
// to a version that contains additions to the database integrity check routine that we would
// like to run on all collections. A missing version number is assumed to be a fresh
// installation of AnkiDroid and we don't run the check.
long current = VersionUtils.getPkgVersionCode();
Timber.i("Current AnkiDroid version: %s", current);
long previous;
if (preferences.contains(UPGRADE_VERSION_KEY)) {
// Upgrading currently installed app
previous = getPreviousVersion(preferences, current);
} else {
// Fresh install
previous = current;
}
preferences.edit().putLong(UPGRADE_VERSION_KEY, current).apply();
// It is rebuilt on the next sync or media check
if (previous < 20300200) {
Timber.i("Deleting media database");
File mediaDb = new File(CollectionHelper.getCurrentAnkiDroidDirectory(this), "collection.media.ad.db2");
if (mediaDb.exists()) {
mediaDb.delete();
}
}
// Recommend the user to do a full-sync if they're upgrading from before 2.3.1beta8
if (previous < 20301208) {
Timber.i("Recommend the user to do a full-sync");
mRecommendFullSync = true;
}
// Fix "font-family" definition in templates created by AnkiDroid before 2.6alhpa23
if (previous < 20600123) {
Timber.i("Fixing font-family definition in templates");
try {
ModelManager models = getCol().getModels();
for (Model m : models.all()) {
String css = m.getString("css");
if (css.contains("font-familiy")) {
m.put("css", css.replace("font-familiy", "font-family"));
models.save(m);
}
}
models.flush();
} catch (JSONException e) {
Timber.e(e, "Failed to upgrade css definitions.");
}
}
// Check if preference upgrade or database check required, otherwise go to new feature screen
int upgradeDbVersion = AnkiDroidApp.CHECK_DB_AT_VERSION;
// Specifying a checkpoint in the future is not supported, please don't do it!
if (current < upgradeDbVersion) {
Timber.e("Invalid value for CHECK_DB_AT_VERSION");
UIUtils.showSimpleSnackbar(this, "Invalid value for CHECK_DB_AT_VERSION", false);
onFinishedStartup();
return;
}
// Skip full DB check if the basic check is OK
// TODO: remove this variable if we really want to do the full db check on every user
boolean skipDbCheck = false;
// if (previous < upgradeDbVersion && getCol().basicCheck()) {
// skipDbCheck = true;
// }
boolean upgradedPreferences = InitialActivity.upgradePreferences(this, previous);
// noinspection ConstantConditions
if (!skipDbCheck && previous < upgradeDbVersion) {
Timber.i("showStartupScreensAndDialogs() running integrityCheck()");
// #5852 - since we may have a warning about disk space, we don't want to force a check database
// and show a warning before the user knows what is happening.
new MaterialDialog.Builder(this).title(R.string.integrity_check_startup_title).content(R.string.integrity_check_startup_content).positiveText(R.string.check_db).negativeText(R.string.close).onPositive((materialDialog, dialogAction) -> integrityCheck()).onNeutral((materialDialog, dialogAction) -> restartActivity()).onNegative((materialDialog, dialogAction) -> restartActivity()).canceledOnTouchOutside(false).cancelable(false).build().show();
return;
}
if (upgradedPreferences) {
Timber.i("Updated preferences with no integrity check - restarting activity");
// If integrityCheck() doesn't occur, but we did update preferences we should restart DeckPicker to
// proceed
restartActivity();
return;
}
// There the "lastVersion" is set, so that this code is not reached again
if (VersionUtils.isReleaseVersion()) {
Timber.i("Displaying new features");
Intent infoIntent = new Intent(this, Info.class);
infoIntent.putExtra(Info.TYPE_EXTRA, Info.TYPE_NEW_VERSION);
if (skip != 0) {
startActivityForResultWithAnimation(infoIntent, SHOW_INFO_NEW_VERSION, START);
} else {
startActivityForResultWithoutAnimation(infoIntent, SHOW_INFO_NEW_VERSION);
}
} else {
Timber.i("Dev Build - not showing 'new features'");
// Don't show new features dialog for development builds
InitialActivity.setUpgradedToLatestVersion(preferences);
String ver = getResources().getString(R.string.updated_version, VersionUtils.getPkgVersionName());
UIUtils.showSnackbar(this, ver, true, -1, null, findViewById(R.id.root_layout), null);
showStartupScreensAndDialogs(preferences, 2);
}
} else {
// This is the main call when there is nothing special required
Timber.i("No startup screens required");
onFinishedStartup();
}
}
use of com.ichi2.libanki.ModelManager in project Anki-Android by ankidroid.
the class ImportTest method addFieldToCurrentModel.
private void addFieldToCurrentModel(String fieldName) throws ConfirmModSchemaException {
ModelManager mm = mTestCol.getModels();
Model m = mm.current();
JSONObject f = mm.newField(fieldName);
mm.addField(m, f);
mm.save(m);
}
Aggregations