use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class Storage method _setColVars.
private static void _setColVars(DB db, @NonNull Time time) {
JSONObject g = new JSONObject(Decks.DEFAULT_DECK);
g.put("id", 1);
g.put("name", "Default");
g.put("conf", 1);
g.put("mod", time.intTime());
JSONObject gc = new JSONObject(Decks.DEFAULT_CONF);
gc.put("id", 1);
JSONObject ag = new JSONObject();
ag.put("1", g);
JSONObject agc = new JSONObject();
agc.put("1", gc);
ContentValues values = new ContentValues();
values.put("conf", Collection.DEFAULT_CONF);
values.put("decks", Utils.jsonToString(ag));
values.put("dconf", Utils.jsonToString(agc));
db.update("col", values);
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class ZipFile method exportInto.
/**
* Export source database into new destination database Note: The following python syntax isn't supported in
* Android: for row in mSrc.db.execute("select * from cards where id in "+ids2str(cids)): therefore we use a
* different method for copying tables
*
* @param path String path to destination database
* @throws JSONException
* @throws IOException
*/
public void exportInto(@NonNull String path, Context context) throws JSONException, IOException, ImportExportException {
// create a new collection at the target
new File(path).delete();
Collection dst = Storage.Collection(context, path);
mSrc = mCol;
// find cards
Long[] cids = cardIds();
// attach dst to src so we can copy data between them. This isn't done in original libanki as Python more
// flexible
dst.close();
Timber.d("Attach DB");
mSrc.getDb().getDatabase().execSQL("ATTACH '" + path + "' AS DST_DB");
// copy cards, noting used nids (as unique set)
Timber.d("Copy cards");
mSrc.getDb().getDatabase().execSQL("INSERT INTO DST_DB.cards select * from cards where id in " + Utils.ids2str(cids));
List<Long> uniqueNids = mSrc.getDb().queryLongList("select distinct nid from cards where id in " + Utils.ids2str(cids));
// notes
Timber.d("Copy notes");
String strnids = Utils.ids2str(uniqueNids);
mSrc.getDb().getDatabase().execSQL("INSERT INTO DST_DB.notes select * from notes where id in " + strnids);
// remove system tags if not exporting scheduling info
if (!mIncludeSched) {
Timber.d("Stripping system tags from list");
ArrayList<String> srcTags = mSrc.getDb().queryStringList("select tags from notes where id in " + strnids);
ArrayList<Object[]> args = new ArrayList<>(srcTags.size());
Object[] arg = new Object[2];
for (int row = 0; row < srcTags.size(); row++) {
arg[0] = removeSystemTags(srcTags.get(row));
arg[1] = uniqueNids.get(row);
args.add(row, arg);
}
mSrc.getDb().executeMany("UPDATE DST_DB.notes set tags=? where id=?", args);
}
// models used by the notes
Timber.d("Finding models used by notes");
ArrayList<Long> mids = mSrc.getDb().queryLongList("select distinct mid from DST_DB.notes where id in " + strnids);
// card history and revlog
if (mIncludeSched) {
Timber.d("Copy history and revlog");
mSrc.getDb().getDatabase().execSQL("insert into DST_DB.revlog select * from revlog where cid in " + Utils.ids2str(cids));
// reopen collection to destination database (different from original python code)
mSrc.getDb().getDatabase().execSQL("DETACH DST_DB");
dst.reopen();
} else {
Timber.d("Detaching destination db and reopening");
// first reopen collection to destination database (different from original python code)
mSrc.getDb().getDatabase().execSQL("DETACH DST_DB");
dst.reopen();
// then need to reset card state
Timber.d("Resetting cards");
dst.getSched().resetCards(cids);
}
// models - start with zero
Timber.d("Copy models");
for (Model m : mSrc.getModels().all()) {
if (mids.contains(m.getLong("id"))) {
dst.getModels().update(m);
}
}
// decks
Timber.d("Copy decks");
java.util.Collection<Long> dids = null;
if (mDid != null) {
dids = new HashSet<>(mSrc.getDecks().children(mDid).values());
dids.add(mDid);
}
JSONObject dconfs = new JSONObject();
for (Deck d : mSrc.getDecks().all()) {
if ("1".equals(d.getString("id"))) {
continue;
}
if (dids != null && !dids.contains(d.getLong("id"))) {
continue;
}
if (d.isStd() && d.getLong("conf") != 1L) {
if (mIncludeSched) {
dconfs.put(Long.toString(d.getLong("conf")), true);
}
}
Deck destinationDeck = d.deepClone();
if (!mIncludeSched) {
// scheduling not included, so reset deck settings to default
destinationDeck.put("conf", 1);
}
dst.getDecks().update(destinationDeck);
}
// copy used deck confs
Timber.d("Copy deck options");
for (DeckConfig dc : mSrc.getDecks().allConf()) {
if (dconfs.has(dc.getString("id"))) {
dst.getDecks().updateConf(dc);
}
}
// find used media
Timber.d("Find used media");
JSONObject media = new JSONObject();
mMediaDir = mSrc.getMedia().dir();
if (mIncludeMedia) {
ArrayList<Long> mid = mSrc.getDb().queryLongList("select mid from notes where id in " + strnids);
ArrayList<String> flds = mSrc.getDb().queryStringList("select flds from notes where id in " + strnids);
for (int idx = 0; idx < mid.size(); idx++) {
for (String file : mSrc.getMedia().filesInStr(mid.get(idx), flds.get(idx))) {
// skip files in subdirs
if (file.contains(File.separator)) {
continue;
}
media.put(file, true);
}
}
if (mMediaDir != null) {
for (File f : new File(mMediaDir).listFiles()) {
if (f.isDirectory()) {
continue;
}
String fname = f.getName();
if (fname.startsWith("_")) {
// Loop through every model that will be exported, and check if it contains a reference to f
for (JSONObject model : mSrc.getModels().all()) {
if (_modelHasMedia(model, fname)) {
media.put(fname, true);
break;
}
}
}
}
}
}
JSONArray keys = media.names();
if (keys != null) {
mMediaFiles.ensureCapacity(keys.length());
addAll(mMediaFiles, keys.stringIterable());
}
Timber.d("Cleanup");
dst.setCrt(mSrc.getCrt());
// todo: tags?
mCount = dst.cardCount();
dst.setMod();
postExport();
dst.close();
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class Collection method basicCheck.
/**
* DB maintenance *********************************************************** ************************************
*/
/*
* Basic integrity check for syncing. True if ok.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean basicCheck() {
// cards without notes
if (mDb.queryScalar("select 1 from cards where nid not in (select id from notes) limit 1") > 0) {
return false;
}
boolean badNotes = mDb.queryScalar("select 1 from notes where id not in (select distinct nid from cards) " + "or mid not in " + Utils.ids2str(getModels().ids()) + " limit 1") > 0;
// notes without cards or models
if (badNotes) {
return false;
}
// invalid ords
for (JSONObject m : getModels().all()) {
// ignore clozes
if (m.getInt("type") != Consts.MODEL_STD) {
continue;
}
// Make a list of valid ords for this model
JSONArray tmpls = m.getJSONArray("tmpls");
boolean badOrd = mDb.queryScalar("select 1 from cards where (ord < 0 or ord >= ?) and nid in ( " + "select id from notes where mid = ?) limit 1", tmpls.length(), m.getLong("id")) > 0;
if (badOrd) {
return false;
}
}
return true;
}
use of com.ichi2.libanki.DB 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.DB in project Anki-Android by ankidroid.
the class ContentProviderTest method testInsertAndRemoveNote.
/**
* Check that inserting and removing a note into default deck works as expected
*/
@Test
public void testInsertAndRemoveNote() {
// Get required objects for test
final ContentResolver cr = getContentResolver();
// Add the note
ContentValues values = new ContentValues();
values.put(FlashCardsContract.Note.MID, mModelId);
values.put(FlashCardsContract.Note.FLDS, Utils.joinFields(TEST_NOTE_FIELDS));
values.put(FlashCardsContract.Note.TAGS, TEST_TAG);
Uri newNoteUri = cr.insert(FlashCardsContract.Note.CONTENT_URI, values);
assertNotNull("Check that URI returned from addNewNote is not null", newNoteUri);
// test that the changes are physically saved to the DB
final Collection col = reopenCol();
// Check that it looks as expected
assertNotNull("check note URI path", newNoteUri.getLastPathSegment());
Note addedNote = new Note(col, Long.parseLong(newNoteUri.getLastPathSegment()));
addedNote.load();
assertArrayEquals("Check that fields were set correctly", addedNote.getFields(), TEST_NOTE_FIELDS);
assertEquals("Check that tag was set correctly", TEST_TAG, addedNote.getTags().get(0));
JSONObject model = col.getModels().get(mModelId);
assertNotNull("Check model", model);
int expectedNumCards = model.getJSONArray("tmpls").length();
assertEquals("Check that correct number of cards generated", expectedNumCards, addedNote.numberOfCards());
// Now delete the note
cr.delete(newNoteUri, null, null);
try {
addedNote.load();
fail("Expected RuntimeException to be thrown when deleting note");
} catch (RuntimeException e) {
// Expect RuntimeException to be thrown when loading deleted note
}
}
Aggregations