use of com.ichi2.anki.exception.ImportExportException in project AnkiChinaAndroid by ankichinateam.
the class ImportTest method testApkg.
@Test
public void testApkg() throws IOException, ImportExportException {
List<String> expected;
List<String> actual;
String apkg = Shared.getTestFilePath(InstrumentationRegistry.getInstrumentation().getTargetContext(), "media.apkg");
Importer imp = new AnkiPackageImporter(testCol, apkg);
expected = Collections.emptyList();
actual = Arrays.asList(new File(testCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(actual.size(), expected.size());
imp.run();
expected = Collections.singletonList("foo.wav");
actual = Arrays.asList(new File(testCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
// import again should be idempotent in terms of media
testCol.remCards(testCol.getDb().queryLongList("select id from cards"));
imp = new AnkiPackageImporter(testCol, apkg);
imp.run();
expected = Collections.singletonList("foo.wav");
actual = Arrays.asList(new File(testCol.getMedia().dir()).list());
actual.retainAll(expected);
assertEquals(expected.size(), actual.size());
// but if the local file has different data, it will rename
testCol.remCards(testCol.getDb().queryLongList("select id from cards"));
FileOutputStream os;
os = new FileOutputStream(new File(testCol.getMedia().dir(), "foo.wav"), false);
os.write("xyz".getBytes());
os.close();
imp = new AnkiPackageImporter(testCol, apkg);
imp.run();
assertEquals(2, new File(testCol.getMedia().dir()).list().length);
}
use of com.ichi2.anki.exception.ImportExportException in project AnkiChinaAndroid by ankichinateam.
the class AnkiPackageImporter method run.
@Override
public void run() throws ImportExportException {
publishProgress(0, 0, 0);
File tempDir = new File(new File(mCol.getPath()).getParent(), "tmpzip");
// self.col into Anki.
Collection tmpCol;
Timber.d("Attempting to import package %s", mFile);
String tmpApkgPath = "";
if (mFile.endsWith(".card")) {
tmpApkgPath = mFile.replace(".card", ".apkg");
AESUtil.decryptionFile(mFile, tmpApkgPath);
mFile = tmpApkgPath;
}
try {
// We extract the zip contents into a temporary directory and do a little more
// validation than the desktop client to ensure the extracted collection is an apkg.
String colname = "collection.anki21";
try {
// extract the deck from the zip file
try {
mZip = new ZipFile(new File(mFile));
} catch (FileNotFoundException fileNotFound) {
// The cache can be cleared between copying the file in and importing. This is temporary
if (fileNotFound.getMessage().contains("ENOENT")) {
mLog.add(getRes().getString(R.string.import_log_file_cache_cleared));
return;
}
// displays: failed to unzip
throw fileNotFound;
}
// v2 scheduler?
if (mZip.getEntry(colname) == null) {
colname = CollectionHelper.COLLECTION_FILENAME;
}
// Make sure we have sufficient free space
long uncompressedSize = Utils.calculateUncompressedSize(mZip);
long availableSpace = Utils.determineBytesAvailable(mCol.getPath());
Timber.d("Total uncompressed size will be: %d", uncompressedSize);
Timber.d("Total available size is: %d", availableSpace);
if (uncompressedSize > availableSpace) {
Timber.e("Not enough space to unzip, need %d, available %d", uncompressedSize, availableSpace);
mLog.add(getRes().getString(R.string.import_log_insufficient_space, uncompressedSize, availableSpace));
return;
}
// The filename that we extract should be collection.anki2
// Importing collection.anki21 fails due to some media regexes expecting collection.anki2.
// We follow how Anki does it and fix the problem here.
HashMap<String, String> mediaToFileNameMap = new HashMap<>();
mediaToFileNameMap.put(colname, CollectionHelper.COLLECTION_FILENAME);
Utils.unzipFiles(mZip, tempDir.getAbsolutePath(), new String[] { colname, "media" }, mediaToFileNameMap);
colname = CollectionHelper.COLLECTION_FILENAME;
} catch (IOException e) {
Timber.e(e, "Failed to unzip apkg.");
AnkiDroidApp.sendExceptionReport(e, "AnkiPackageImporter::run() - unzip");
mLog.add(getRes().getString(R.string.import_log_failed_unzip, e.getLocalizedMessage()));
return;
}
String colpath = new File(tempDir, colname).getAbsolutePath();
if (!(new File(colpath)).exists()) {
mLog.add(getRes().getString(R.string.import_log_failed_copy_to, colpath));
return;
}
tmpCol = Storage.Collection(mContext, colpath);
try {
if (!tmpCol.validCollection()) {
mLog.add(getRes().getString(R.string.import_log_failed_validate));
return;
}
} finally {
if (tmpCol != null) {
tmpCol.close();
}
}
mFile = colpath;
// we need the media dict in advance, and we'll need a map of fname ->
// number to use during the import
File mediaMapFile = new File(tempDir, "media");
mNameToNum = new HashMap<>();
String dirPath = tmpCol.getMedia().dir();
File dir = new File(dirPath);
// We need the opposite mapping in AnkiDroid since our extraction method requires it.
Map<String, String> numToName = new HashMap<>();
try (JsonReader jr = new JsonReader(new FileReader(mediaMapFile))) {
jr.beginObject();
// v in anki
String name;
// k in anki
String num;
while (jr.hasNext()) {
num = jr.nextName();
name = jr.nextString();
File file = new File(dir, name);
if (!Utils.isInside(file, dir)) {
throw (new RuntimeException("Invalid file"));
}
Utils.nfcNormalized(num);
mNameToNum.put(name, num);
numToName.put(num, name);
}
jr.endObject();
} catch (FileNotFoundException e) {
Timber.e("Apkg did not contain a media dict. No media will be imported.");
} catch (IOException e) {
Timber.e("Malformed media dict. Media import will be incomplete.");
}
// run anki2 importer
super.run();
// import static media
for (Map.Entry<String, String> entry : mNameToNum.entrySet()) {
String file = entry.getKey();
String c = entry.getValue();
if (!file.startsWith("_") && !file.startsWith("latex-")) {
continue;
}
File path = new File(mCol.getMedia().dir(), Utils.nfcNormalized(file));
if (!path.exists()) {
try {
Utils.unzipFiles(mZip, mCol.getMedia().dir(), new String[] { c }, numToName);
} catch (IOException e) {
Timber.e("Failed to extract static media file. Ignoring.");
}
}
}
} finally {
long availableSpace = Utils.determineBytesAvailable(mCol.getPath());
Timber.d("Total available size is: %d", availableSpace);
// Clean up our temporary files
if (tempDir.exists()) {
BackupManager.removeDir(tempDir);
}
}
publishProgress(100, 100, 100);
// if(!tmpApkgPath.isEmpty()){
// new File(tmpApkgPath).delete();
// }
}
use of com.ichi2.anki.exception.ImportExportException in project AnkiChinaAndroid by ankichinateam.
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(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));
Set<Long> nids = new HashSet<>(mSrc.getDb().queryLongList("select nid from cards where id in " + Utils.ids2str(cids)));
// notes
Timber.d("Copy notes");
ArrayList<Long> uniqueNids = new ArrayList<>(nids);
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"))) {
Timber.d("Copy models:%s", m.getLong("id"));
dst.getModels().update(m);
}
}
for (Model m : dst.getModels().all()) {
Timber.d("check dst model:%s", m.getLong("id"));
}
// decks
Timber.d("Copy decks");
ArrayList<Long> dids = new ArrayList<>();
if (mDid != null) {
dids.add(mDid);
for (Long x : mSrc.getDecks().children(mDid).values()) {
dids.add(x);
}
}
JSONObject dconfs = new JSONObject();
for (Deck d : mSrc.getDecks().all()) {
if ("1".equals(d.getString("id"))) {
continue;
}
if (mDid != null && !dids.contains(d.getLong("id"))) {
continue;
}
if (d.getInt("dyn") != 1 && 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) {
for (int i = 0; i < keys.length(); i++) {
mMediaFiles.add(keys.getString(i));
}
}
Timber.d("Cleanup");
dst.setCrt(mSrc.getCrt());
// todo: tags?
mCount = dst.cardCount();
dst.setMod();
postExport();
dst.close();
}
use of com.ichi2.anki.exception.ImportExportException in project AnkiChinaAndroid by ankichinateam.
the class AnkiPackageExporterTest method missingFileInDeckExportDoesSkipsFile.
@Test
public void missingFileInDeckExportDoesSkipsFile() throws IOException, ImportExportException {
// arrange
File mediaFilePath = addTempFileToMediaAndNote();
if (!mediaFilePath.delete()) {
throw new IllegalStateException("need to delete temp file for test to pass");
}
AnkiPackageExporter exporter = getExporterForDeckWithMedia();
Path tempExportDir = Files.createTempDirectory("AnkiDroid-missingFileInExportDoesNotThrowException-export");
File exportedFile = new File(tempExportDir.toFile(), "export.apkg");
// act
exporter.exportInto(exportedFile.getAbsolutePath(), getTargetContext());
// assert
Path unzipDirectory = unzipFilesTo(tempExportDir, exportedFile);
File[] files = unzipDirectory.toFile().listFiles();
// confirm the files
List<String> fileNames = Arrays.stream(files).map(File::getName).collect(Collectors.toList());
assertThat(fileNames, containsInAnyOrder("collection.anki2", "media"));
assertThat("Only two files should exist", fileNames, hasSize(2));
checkMediaExportStringIs(files, "{}");
}
use of com.ichi2.anki.exception.ImportExportException in project AnkiChinaAndroid by ankichinateam.
the class AnkiPackageExporterTest method fileInExportIsCopied.
@Test
public void fileInExportIsCopied() throws IOException, ImportExportException {
// arrange
File tempFileInCollection = addTempFileToMediaAndNote();
AnkiPackageExporter exporter = getExporterForDeckWithMedia();
Path tempExportDir = Files.createTempDirectory("AnkiDroid-missingFileInExportDoesNotThrowException-export");
File exportedFile = new File(tempExportDir.toFile(), "export.apkg");
// act
exporter.exportInto(exportedFile.getAbsolutePath(), getTargetContext());
// assert
Path unzipDirectory = unzipFilesTo(tempExportDir, exportedFile);
File[] files = unzipDirectory.toFile().listFiles();
// confirm the files
List<String> fileNames = Arrays.stream(files).map(File::getName).collect(Collectors.toList());
assertThat(fileNames, containsInAnyOrder("collection.anki2", "media", "0"));
assertThat("Three files should exist", fileNames, hasSize(3));
// {"0":"filename.txt"}
String expected = String.format("{\"0\":\"%s\"}", tempFileInCollection.getName());
checkMediaExportStringIs(files, expected);
}
Aggregations