Search in sources :

Example 6 with DB

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);
}
Also used : AbstractSched(com.ichi2.libanki.sched.AbstractSched) Collection(com.ichi2.libanki.Collection) DB(com.ichi2.libanki.DB) Card(com.ichi2.libanki.Card)

Example 7 with DB

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);
    }
}
Also used : HashMap(java.util.HashMap) FileNotFoundException(java.io.FileNotFoundException) Time(com.ichi2.libanki.utils.Time) IOException(java.io.IOException) JSONException(com.ichi2.utils.JSONException) CancellationException(java.util.concurrent.CancellationException) FileNotFoundException(java.io.FileNotFoundException) ConfirmModSchemaException(com.ichi2.anki.exception.ConfirmModSchemaException) ImportExportException(com.ichi2.anki.exception.ImportExportException) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException) ZipFile(org.apache.commons.compress.archivers.zip.ZipFile) Collection(com.ichi2.libanki.Collection) JsonReader(com.google.gson.stream.JsonReader) FileReader(java.io.FileReader) Resources(android.content.res.Resources) ZipFile(org.apache.commons.compress.archivers.zip.ZipFile) File(java.io.File) Map(java.util.Map) HashMap(java.util.HashMap) TreeMap(java.util.TreeMap)

Example 8 with DB

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);
}
Also used : Collection(com.ichi2.libanki.Collection) List(java.util.List) ArrayList(java.util.ArrayList) LinkedList(java.util.LinkedList)

Example 9 with DB

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();
        }
    }
}
Also used : ZipEntry(java.util.zip.ZipEntry) ArrayList(java.util.ArrayList) JSONArray(com.ichi2.utils.JSONArray) FileNotFoundException(java.io.FileNotFoundException) IOException(java.io.IOException) Cursor(android.database.Cursor) FileInputStream(java.io.FileInputStream) BufferedInputStream(java.io.BufferedInputStream) ZipOutputStream(java.util.zip.ZipOutputStream) FileOutputStream(java.io.FileOutputStream) ZipFile(java.util.zip.ZipFile) File(java.io.File) BufferedOutputStream(java.io.BufferedOutputStream) Pair(android.util.Pair)

Example 10 with DB

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();
    }
}
Also used : JSONObject(com.ichi2.utils.JSONObject) BufferedInputStream(java.io.BufferedInputStream) FileInputStream(java.io.FileInputStream) InputStream(java.io.InputStream) ZipEntry(java.util.zip.ZipEntry) ArrayList(java.util.ArrayList)

Aggregations

File (java.io.File)19 JSONObject (com.ichi2.utils.JSONObject)18 Collection (com.ichi2.libanki.Collection)14 IOException (java.io.IOException)14 DB (com.ichi2.libanki.DB)13 FileNotFoundException (java.io.FileNotFoundException)13 ArrayList (java.util.ArrayList)11 JSONArray (com.ichi2.utils.JSONArray)10 Test (org.junit.Test)10 ConfirmModSchemaException (com.ichi2.anki.exception.ConfirmModSchemaException)9 ContentValues (android.content.ContentValues)8 Resources (android.content.res.Resources)8 Uri (android.net.Uri)8 JSONException (com.ichi2.utils.JSONException)8 Cursor (android.database.Cursor)7 Model (com.ichi2.libanki.Model)7 ContentResolver (android.content.ContentResolver)6 FileInputStream (java.io.FileInputStream)6 SQLException (android.database.SQLException)4 BufferedInputStream (java.io.BufferedInputStream)4