use of com.ichi2.async.CollectionTask.TASK_TYPE.IMPORT in project AnkiChinaAndroid by ankichinateam.
the class NoteImporter method importNotes.
/**
* Convert each card into a note, apply attributes and add to col.
*/
public void importNotes(List<ForeignNote> notes) {
Assert.that(mappingOk());
// note whether tags are mapped
mTagsMapped = false;
for (String f : mMapping) {
if ("_tags".equals(f)) {
mTagsMapped = true;
break;
}
}
// gather checks for duplicate comparison
HashMap<Long, List<Long>> csums = new HashMap<>();
try (Cursor c = mCol.getDb().query("select csum, id from notes where mid = ?", mModel.getLong("id"))) {
while (c.moveToNext()) {
long csum = c.getLong(0);
long id = c.getLong(1);
if (csums.containsKey(csum)) {
csums.get(csum).add(id);
} else {
csums.put(csum, new ArrayList<>(Collections.singletonList(id)));
}
}
}
HashMap<String, Boolean> firsts = new HashMap<>();
int fld0index = mMapping.indexOf(mModel.getJSONArray("flds").getJSONObject(0).getString("name"));
mFMap = mCol.getModels().fieldMap(mModel);
mNextId = mCol.getTime().timestampID(mCol.getDb(), "notes");
// loop through the notes
List<Object[]> updates = new ArrayList<>();
List<String> updateLog = new ArrayList<>();
// PORT: Translations moved closer to their sources
List<Object[]> _new = new ArrayList<>();
_ids = new ArrayList<>();
_cards = new ArrayList<>();
mEmptyNotes = false;
int dupeCount = 0;
List<String> dupes = new ArrayList<>();
for (ForeignNote n : notes) {
for (int c = 0; c < n.mFields.size(); c++) {
if (!this.mAllowHTML) {
n.mFields.set(c, HtmlUtils.escape(n.mFields.get(c)));
}
n.mFields.set(c, n.mFields.get(c).trim());
if (!this.mAllowHTML) {
n.mFields.set(c, n.mFields.get(c).replace("\n", "<br>"));
}
}
String fld0 = n.mFields.get(fld0index);
long csum = fieldChecksum(fld0);
// first field must exist
if (fld0 == null || fld0.length() == 0) {
getLog().add(getString(R.string.note_importer_error_empty_first_field, TextUtils.join(" ", n.mFields)));
continue;
}
// earlier in import?
if (firsts.containsKey(fld0) && mImportMode != ADD_MODE) {
// duplicates in source file; log and ignore
getLog().add(getString(R.string.note_importer_error_appeared_twice, fld0));
continue;
}
firsts.put(fld0, true);
// already exists?
boolean found = false;
if (csums.containsKey(csum)) {
// csum is not a guarantee; have to check
for (Long id : csums.get(csum)) {
String flds = mCol.getDb().queryString("select flds from notes where id = ?", id);
String[] sflds = splitFields(flds);
if (fld0.equals(sflds[0])) {
// duplicate
found = true;
if (mImportMode == UPDATE_MODE) {
Object[] data = updateData(n, id, sflds);
if (data != null && data.length > 0) {
updates.add(data);
updateLog.add(getString(R.string.note_importer_error_first_field_matched, fld0));
dupeCount += 1;
found = true;
}
} else if (mImportMode == IGNORE_MODE) {
dupeCount += 1;
} else if (mImportMode == ADD_MODE) {
// allow duplicates in this case
if (!dupes.contains(fld0)) {
// only show message once, no matter how many
// duplicates are in the collection already
updateLog.add(getString(R.string.note_importer_error_added_duplicate_first_field, fld0));
dupes.add(fld0);
}
found = false;
}
}
}
}
// newly add
if (!found) {
Object[] data = newData(n);
if (data != null && data.length > 0) {
_new.add(data);
// note that we've seen this note once already
firsts.put(fld0, true);
}
}
}
addNew(_new);
addUpdates(updates);
// make sure to update sflds, etc
mCol.updateFieldCache(collection2Array(_ids));
// generate cards
if (!mCol.genCards(_ids).isEmpty()) {
this.getLog().add(0, getString(R.string.note_importer_empty_cards_found));
}
// apply scheduling updates
updateCards();
// we randomize or order here, to ensure that siblings
// have the same due#
long did = mCol.getDecks().selected();
DeckConfig conf = mCol.getDecks().confForDid(did);
// in order due?
if (conf.getJSONObject("new").getInt("order") == NEW_CARDS_RANDOM) {
mCol.getSched().randomizeCards(did);
}
String part1 = getQuantityString(R.plurals.note_importer_notes_added, _new.size());
String part2 = getQuantityString(R.plurals.note_importer_notes_updated, mUpdateCount);
int unchanged;
if (mImportMode == UPDATE_MODE) {
unchanged = dupeCount - mUpdateCount;
} else if (mImportMode == IGNORE_MODE) {
unchanged = dupeCount;
} else {
unchanged = 0;
}
String part3 = getQuantityString(R.plurals.note_importer_notes_unchanged, unchanged);
mLog.add(String.format("%s, %s, %s.", part1, part2, part3));
mLog.addAll(updateLog);
if (mEmptyNotes) {
mLog.add(getString(R.string.note_importer_error_empty_notes));
}
mTotal = _ids.size();
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.IMPORT in project Anki-Android by ankidroid.
the class DeckPicker method onOptionsItemSelected.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Resources res = getResources();
if (getDrawerToggle().onOptionsItemSelected(item)) {
return true;
}
int itemId = item.getItemId();
if (itemId == R.id.action_undo) {
Timber.i("DeckPicker:: Undo button pressed");
undo();
return true;
} else if (itemId == R.id.action_sync) {
Timber.i("DeckPicker:: Sync button pressed");
sync();
return true;
} else if (itemId == R.id.action_import) {
Timber.i("DeckPicker:: Import button pressed");
showDialogFragment(ImportFileSelectionFragment.createInstance(this));
return true;
} else if (itemId == R.id.action_new_filtered_deck) {
CreateDeckDialog createFilteredDeckDialog = new CreateDeckDialog(DeckPicker.this, R.string.new_deck, CreateDeckDialog.DeckDialogType.FILTERED_DECK, null);
createFilteredDeckDialog.setOnNewDeckCreated((id) -> {
// a filtered deck was created
openStudyOptions(true);
});
createFilteredDeckDialog.showFilteredDeckDialog();
return true;
} else if (itemId == R.id.action_check_database) {
Timber.i("DeckPicker:: Check database button pressed");
showDatabaseErrorDialog(DatabaseErrorDialog.DIALOG_CONFIRM_DATABASE_CHECK);
return true;
} else if (itemId == R.id.action_check_media) {
Timber.i("DeckPicker:: Check media button pressed");
showMediaCheckDialog(MediaCheckDialog.DIALOG_CONFIRM_MEDIA_CHECK);
return true;
} else if (itemId == R.id.action_empty_cards) {
Timber.i("DeckPicker:: Empty cards button pressed");
handleEmptyCards();
return true;
} else if (itemId == R.id.action_model_browser_open) {
Timber.i("DeckPicker:: Model browser button pressed");
Intent noteTypeBrowser = new Intent(this, ModelBrowser.class);
startActivityForResultWithAnimation(noteTypeBrowser, 0, START);
return true;
} else if (itemId == R.id.action_restore_backup) {
Timber.i("DeckPicker:: Restore from backup button pressed");
showDatabaseErrorDialog(DatabaseErrorDialog.DIALOG_CONFIRM_RESTORE_BACKUP);
return true;
} else if (itemId == R.id.action_export) {
Timber.i("DeckPicker:: Export collection button pressed");
String msg = getResources().getString(R.string.confirm_apkg_export);
mExportingDelegate.showExportDialog(msg);
return true;
}
return super.onOptionsItemSelected(item);
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.IMPORT in project Anki-Android by ankidroid.
the class ImportTest method testAnki2Mediadupes.
@Test
public void testAnki2Mediadupes() throws IOException, JSONException, ImportExportException {
// add a note that references a sound
Note n = mTestCol.newNote();
n.setField(0, "[sound:foo.mp3]");
long mid = n.model().getLong("id");
mTestCol.addNote(n);
// add that sound to the media directory
FileOutputStream os = new FileOutputStream(new File(mTestCol.getMedia().dir(), "foo.mp3"), false);
os.write("foo".getBytes());
os.close();
mTestCol.close();
// it should be imported correctly into an empty deck
Collection empty = getEmptyCol();
Importer imp = new Anki2Importer(empty, mTestCol.getPath());
imp.run();
List<String> expected = Collections.singletonList("foo.mp3");
List<String> actual = Arrays.asList(new File(empty.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
// and importing again will not duplicate, as the file content matches
empty.remCards(empty.getDb().queryLongList("select id from cards"));
imp = new Anki2Importer(empty, mTestCol.getPath());
imp.run();
expected = Collections.singletonList("foo.mp3");
actual = Arrays.asList(new File(empty.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
assertTrue(n.getFields()[0].contains("foo.mp3"));
// if the local file content is different, and import should trigger a rename
empty.remCards(empty.getDb().queryLongList("select id from cards"));
os = new FileOutputStream(new File(empty.getMedia().dir(), "foo.mp3"), false);
os.write("bar".getBytes());
os.close();
imp = new Anki2Importer(empty, mTestCol.getPath());
imp.run();
expected = Arrays.asList("foo.mp3", String.format("foo_%s.mp3", mid));
actual = Arrays.asList(new File(empty.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
assertTrue(n.getFields()[0].contains("_"));
// if the localized media file already exists, we rewrite the note and media
empty.remCards(empty.getDb().queryLongList("select id from cards"));
os = new FileOutputStream(new File(empty.getMedia().dir(), "foo.mp3"));
os.write("bar".getBytes());
os.close();
imp = new Anki2Importer(empty, mTestCol.getPath());
imp.run();
expected = Arrays.asList("foo.mp3", String.format("foo_%s.mp3", mid));
actual = Arrays.asList(new File(empty.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
assertTrue(n.getFields()[0].contains("_"));
empty.close();
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.IMPORT in project Anki-Android by ankidroid.
the class ImportTest method testApkg.
@Test
public void testApkg() throws IOException, ImportExportException {
String apkg = Shared.getTestFilePath(getTestContext(), "media.apkg");
Importer imp = new AnkiPackageImporter(mTestCol, apkg);
List<String> expected = Collections.emptyList();
List<String> actual = Arrays.asList(new File(mTestCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(actual.size(), expected.size());
imp.run();
expected = Collections.singletonList("foo.wav");
actual = Arrays.asList(new File(mTestCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
// import again should be idempotent in terms of media
mTestCol.remCards(mTestCol.getDb().queryLongList("select id from cards"));
imp = new AnkiPackageImporter(mTestCol, apkg);
imp.run();
expected = Collections.singletonList("foo.wav");
actual = Arrays.asList(new File(mTestCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
// but if the local file has different data, it will rename
mTestCol.remCards(mTestCol.getDb().queryLongList("select id from cards"));
FileOutputStream os = new FileOutputStream(new File(mTestCol.getMedia().dir(), "foo.wav"), false);
os.write("xyz".getBytes());
os.close();
imp = new AnkiPackageImporter(mTestCol, apkg);
imp.run();
assertEquals(2, new File(mTestCol.getMedia().dir()).list().length);
}
use of com.ichi2.async.CollectionTask.TASK_TYPE.IMPORT in project Anki-Android by ankidroid.
the class Collection method addNote.
/**
* Add a note and cards to the collection. If allowEmpty, at least one card is generated.
* @param note The note to add to the collection
* @param allowEmpty Whether we accept to add it even if it should generate no card. Useful to import note even if buggy
* @return Number of card added
*/
public int addNote(@NonNull Note note, Models.AllowEmpty allowEmpty) {
// check we have card models available, then save
ArrayList<JSONObject> cms = findTemplates(note, allowEmpty);
// Todo: upstream, we accept to add a not even if it generates no card. Should be ported to ankidroid
if (cms.isEmpty()) {
return 0;
}
note.flush();
// deck conf governs which of these are used
int due = nextID("pos");
// add cards
int ncards = 0;
for (JSONObject template : cms) {
_newCard(note, template, due);
ncards += 1;
}
return ncards;
}
Aggregations