use of com.ichi2.libanki.DB in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundAnswerCard.
private TaskData doInBackgroundAnswerCard(TaskData param) {
Collection col = getCol();
AbstractSched sched = col.getSched();
Card oldCard = param.getCard();
@Consts.BUTTON_TYPE int ease = param.getInt();
Card newCard = null;
Timber.i(oldCard != null ? "Answering card" : "Obtaining card");
try {
DB db = col.getDb();
db.getDatabase().beginTransaction();
try {
if (oldCard != null) {
Timber.i("Answering card %d", oldCard.getId());
sched.answerCard(oldCard, ease);
}
newCard = sched.getCard();
if (newCard != null) {
// render cards before locking database
newCard._getQA(true);
}
publishProgress(new TaskData(newCard));
db.getDatabase().setTransactionSuccessful();
} finally {
db.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
Timber.e(e, "doInBackgroundAnswerCard - RuntimeException on answering card");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundAnswerCard");
return new TaskData(false);
}
return new TaskData(true);
}
use of com.ichi2.libanki.DB in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundImportReplace.
private TaskData doInBackgroundImportReplace(TaskData param) {
Timber.d("doInBackgroundImportReplace");
String path = param.getString();
Resources res = AnkiDroidApp.getInstance().getBaseContext().getResources();
// extract the deck from the zip file
String colPath = CollectionHelper.getCollectionPath(mContext);
File dir = new File(new File(colPath).getParentFile(), "tmpzip");
if (dir.exists()) {
BackupManager.removeDir(dir);
}
// from anki2.py
String colname = "collection.anki21";
ZipFile zip;
try {
zip = new ZipFile(new File(path));
} catch (IOException e) {
Timber.e(e, "doInBackgroundImportReplace - Error while unzipping");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace0");
return new TaskData(false);
}
try {
// v2 scheduler?
if (zip.getEntry(colname) == null) {
colname = CollectionHelper.COLLECTION_FILENAME;
}
Utils.unzipFiles(zip, dir.getAbsolutePath(), new String[] { colname, "media" }, null);
} catch (IOException e) {
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace - unzip");
return new TaskData(false);
}
String colFile = new File(dir, colname).getAbsolutePath();
if (!(new File(colFile)).exists()) {
return new TaskData(false);
}
Collection tmpCol = null;
try {
tmpCol = Storage.Collection(mContext, colFile);
if (!tmpCol.validCollection()) {
tmpCol.close();
return new TaskData(false);
}
} catch (Exception e) {
Timber.e("Error opening new collection file... probably it's invalid");
try {
tmpCol.close();
} catch (Exception e2) {
// do nothing
}
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace - open col");
return new TaskData(false);
} finally {
if (tmpCol != null) {
tmpCol.close();
}
}
publishProgress(new TaskData(res.getString(R.string.importing_collection)));
if (hasValidCol()) {
// unload collection and trigger a backup
Time time = CollectionHelper.getInstance().getTimeSafe(mContext);
CollectionHelper.getInstance().closeCollection(true, "Importing new collection");
CollectionHelper.getInstance().lockCollection();
BackupManager.performBackupInBackground(colPath, true, time);
}
// overwrite collection
File f = new File(colFile);
if (!f.renameTo(new File(colPath))) {
// Exit early if this didn't work
return new TaskData(false);
}
int addedCount = -1;
try {
CollectionHelper.getInstance().unlockCollection();
// because users don't have a backup of media, it's safer to import new
// data and rely on them running a media db check to get rid of any
// unwanted media. in the future we might also want to duplicate this step
// import media
HashMap<String, String> nameToNum = new HashMap<>();
HashMap<String, String> numToName = new HashMap<>();
File mediaMapFile = new File(dir.getAbsolutePath(), "media");
if (mediaMapFile.exists()) {
JsonReader jr = new JsonReader(new FileReader(mediaMapFile));
jr.beginObject();
String name;
String num;
while (jr.hasNext()) {
num = jr.nextName();
name = jr.nextString();
nameToNum.put(name, num);
numToName.put(num, name);
}
jr.endObject();
jr.close();
}
String mediaDir = Media.getCollectionMediaPath(colPath);
int total = nameToNum.size();
int i = 0;
for (Map.Entry<String, String> entry : nameToNum.entrySet()) {
String file = entry.getKey();
String c = entry.getValue();
File of = new File(mediaDir, file);
if (!of.exists()) {
Utils.unzipFiles(zip, mediaDir, new String[] { c }, numToName);
}
++i;
publishProgress(new TaskData(res.getString(R.string.import_media_count, (i + 1) * 100 / total)));
}
zip.close();
// delete tmp dir
BackupManager.removeDir(dir);
return new TaskData(true);
} catch (RuntimeException e) {
Timber.e(e, "doInBackgroundImportReplace - RuntimeException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace1");
return new TaskData(false);
} catch (FileNotFoundException e) {
Timber.e(e, "doInBackgroundImportReplace - FileNotFoundException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace2");
return new TaskData(false);
} catch (IOException e) {
Timber.e(e, "doInBackgroundImportReplace - IOException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace3");
return new TaskData(false);
}
}
use of com.ichi2.libanki.DB in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundCheckMedia.
/**
* @return The results list from the check, or false if any errors.
*/
private TaskData doInBackgroundCheckMedia() {
Timber.d("doInBackgroundCheckMedia");
Collection col = getCol();
// A media check on AnkiDroid will also update the media db
col.getMedia().findChanges(true);
// Then do the actual check
List<List<String>> result = col.getMedia().check();
return new TaskData(0, new Object[] { result }, true);
}
use of com.ichi2.libanki.DB in project AnkiChinaAndroid by ankichinateam.
the class Media method mediaChangesZip.
/**
* Media syncing: zips
* ***********************************************************
*/
/**
* Unlike python, our temp zip file will be on disk instead of in memory. This avoids storing
* potentially large files in memory which is not feasible with Android's limited heap space.
* <p>
* Notes:
* <p>
* - The maximum size of the changes zip is decided by the constant SYNC_ZIP_SIZE. If a media file exceeds this
* limit, only that file (in full) will be zipped to be sent to the server.
* <p>
* - This method will be repeatedly called from MediaSyncer until there are no more files (marked "dirty" in the DB)
* to send.
* <p>
* - Since AnkiDroid avoids scanning the media folder on every sync, it is possible for a file to be marked as a
* new addition but actually have been deleted (e.g., with a file manager). In this case we skip over the file
* and mark it as removed in the database. (This behaviour differs from the desktop client).
* <p>
*/
public Pair<File, List<String>> mediaChangesZip() {
File f = new File(mCol.getPath().replaceFirst("collection\\.anki2$", "tmpSyncToServer.zip"));
Cursor cur = null;
try (ZipOutputStream z = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)))) {
z.setMethod(ZipOutputStream.DEFLATED);
List<String> fnames = new ArrayList<>();
// meta is a list of (fname, zipname), where zipname of null is a deleted file
// NOTE: In python, meta is a list of tuples that then gets serialized into json and added
// to the zip as a string. In our version, we use JSON objects from the start to avoid the
// serialization step. Instead of a list of tuples, we use JSONArrays of JSONArrays.
JSONArray meta = new JSONArray();
int sz = 0;
byte[] buffer = new byte[2048];
cur = mDb.getDatabase().query("select fname, csum from media where dirty=1 limit " + Consts.SYNC_ZIP_COUNT, null);
for (int c = 0; cur.moveToNext(); c++) {
String fname = cur.getString(0);
String csum = cur.getString(1);
fnames.add(fname);
String normname = Utils.nfcNormalized(fname);
if (!TextUtils.isEmpty(csum)) {
try {
mCol.log("+media zip " + fname);
File file = new File(dir(), fname);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), 2048);
z.putNextEntry(new ZipEntry(Integer.toString(c)));
int count = 0;
while ((count = bis.read(buffer, 0, 2048)) != -1) {
z.write(buffer, 0, count);
}
z.closeEntry();
bis.close();
meta.put(new JSONArray().put(normname).put(Integer.toString(c)));
sz += file.length();
} catch (FileNotFoundException e) {
// A file has been marked as added but no longer exists in the media directory.
// Skip over it and mark it as removed in the db.
removeFile(fname);
}
} else {
mCol.log("-media zip " + fname);
meta.put(new JSONArray().put(normname).put(""));
}
if (sz >= Consts.SYNC_ZIP_SIZE) {
break;
}
}
z.putNextEntry(new ZipEntry("_meta"));
z.write(Utils.jsonToString(meta).getBytes());
z.closeEntry();
// Don't leave lingering temp files if the VM terminates.
f.deleteOnExit();
return new Pair<>(f, fnames);
} catch (IOException e) {
Timber.e(e, "Failed to create media changes zip: ");
throw new RuntimeException(e);
} finally {
if (cur != null) {
cur.close();
}
}
}
use of com.ichi2.libanki.DB in project AnkiChinaAndroid by ankichinateam.
the class Media method addFilesFromZip.
/**
* Extract zip data; return the number of files extracted. Unlike the python version, this method consumes a
* ZipFile stored on disk instead of a String buffer. Holding the entire downloaded data in memory is not feasible
* since some devices can have very limited heap space.
* <p>
* This method closes the file before it returns.
*/
public int addFilesFromZip(ZipFile z) throws IOException {
try {
List<Object[]> media = new ArrayList<>();
// get meta info first
JSONObject meta = new JSONObject(Utils.convertStreamToString(z.getInputStream(z.getEntry("_meta"))));
// then loop through all files
int cnt = 0;
for (ZipEntry i : Collections.list(z.entries())) {
if ("_meta".equals(i.getName())) {
// ignore previously-retrieved meta
continue;
} else {
String name = meta.getString(i.getName());
// normalize name for platform
name = Utils.nfcNormalized(name);
// save file
String destPath = dir().concat(File.separator).concat(name);
try (InputStream zipInputStream = z.getInputStream(i)) {
Utils.writeToFile(zipInputStream, destPath);
}
String csum = Utils.fileChecksum(destPath);
// update db
media.add(new Object[] { name, csum, _mtime(destPath), 0 });
cnt += 1;
}
}
if (media.size() > 0) {
mDb.executeMany("insert or replace into media values (?,?,?,?)", media);
}
return cnt;
} finally {
z.close();
}
}
Aggregations