Search in sources :

Example 6 with Media

use of com.ichi2.libanki.Media in project AnkiChinaAndroid by ankichinateam.

the class ZipFile method exportFiltered.

private JSONObject exportFiltered(ZipFile z, String path, Context context) throws IOException, JSONException, ImportExportException {
    // export into the anki2 file
    String colfile = path.replace(".apkg", ".anki2");
    super.exportInto(colfile, context);
    z.write(colfile, CollectionHelper.COLLECTION_FILENAME);
    // and media
    prepareMedia();
    JSONObject media = _exportMedia(z, mMediaFiles, mCol.getMedia().dir());
    // tidy up intermediate files
    SQLiteDatabase.deleteDatabase(new File(colfile));
    SQLiteDatabase.deleteDatabase(new File(path.replace(".apkg", ".media.ad.db2")));
    String tempPath = path.replace(".apkg", ".media");
    File file = new File(tempPath);
    if (file.exists()) {
        String deleteCmd = "rm -r " + tempPath;
        Runtime runtime = Runtime.getRuntime();
        try {
            runtime.exec(deleteCmd);
        } catch (IOException e) {
        }
    }
    return media;
}
Also used : JSONObject(com.ichi2.utils.JSONObject) IOException(java.io.IOException) File(java.io.File)

Example 7 with Media

use of com.ichi2.libanki.Media 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 8 with Media

use of com.ichi2.libanki.Media 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)

Example 9 with Media

use of com.ichi2.libanki.Media in project AnkiChinaAndroid by ankichinateam.

the class Media method filesInStr.

/**
 * Extract media filenames from an HTML string.
 *
 * @param string        The string to scan for media filenames ([sound:...] or <img...>).
 * @param includeRemote If true will also include external http/https/ftp urls.
 * @return A list containing all the sound and image filenames found in the input string.
 */
public static List<String> filesInStr(Collection mCol, Long mid, String string, boolean includeRemote) {
    List<String> l = new ArrayList<>();
    JSONObject model = mCol.getModels().get(mid);
    List<String> strings = new ArrayList<>();
    if (model.getInt("type") == Consts.MODEL_CLOZE && string.contains("{{c")) {
        // if the field has clozes in it, we'll need to expand the
        // possibilities so we can render latex
        strings = _expandClozes(string);
    } else {
        strings.add(string);
    }
    for (String s : strings) {
        // handle latex
        s = LaTeX.mungeQA(s, mCol, model);
        // extract filenames
        Matcher m;
        for (Pattern p : mRegexps) {
            // NOTE: python uses the named group 'fname'. Java doesn't have named groups, so we have to determine
            // the index based on which pattern we are using
            int fnameIdx = p.equals(fSoundRegexps) ? 2 : p.equals(fImgRegExpU) ? 2 : 3;
            m = p.matcher(s);
            while (m.find()) {
                String fname = m.group(fnameIdx);
                boolean isLocal = !fRemotePattern.matcher(fname.toLowerCase(Locale.US)).find();
                if (isLocal || includeRemote) {
                    l.add(fname);
                }
            }
        }
    }
    return l;
}
Also used : Pattern(java.util.regex.Pattern) JSONObject(com.ichi2.utils.JSONObject) Matcher(java.util.regex.Matcher) ArrayList(java.util.ArrayList)

Example 10 with Media

use of com.ichi2.libanki.Media in project AnkiChinaAndroid by ankichinateam.

the class AnkiChinaSyncer method uploadLocalMediaFileInfo.

/**
 * 上传媒体文件信息给服务端,服务端比对完后下发文件的同步结果
 */
private void uploadLocalMediaFileInfo(String token) {
    // 获取本地media文件夹的目录
    updateDialogProgress(SYNCING_MEDIA, "", 1);
    File mediaDir = new File(Media.getCollectionMediaPath(CollectionHelper.getInstance().getColSafe(AnkiDroidApp.getInstance()).getPath()));
    // if (!mediaDir.exists()) {
    // return;
    // }
    File[] files = mediaDir.listFiles();
    // if (files.length == 0) {
    // return;
    // }
    String[] localFiles = new String[files.length];
    for (int i = 0; i < files.length; i++) {
        localFiles[i] = files[i].getName();
    }
    JSONArray filesJson = new JSONArray(localFiles);
    // Timber.i("local file list:%s", filesJson);
    RequestBody formBody = new FormBody.Builder().add("file_list", filesJson.toString()).build();
    updateDialogProgress(SYNCING_MEDIA, "", 5);
    if (mCancel) {
        return;
    }
    OKHttpUtil.post(Consts.ANKI_CHINA_BASE + Consts.API_VERSION + "napi/sync/postFileInfo", formBody, token, "", new OKHttpUtil.MyCallBack() {

        @Override
        public void onFailure(Call call, IOException e) {
            updateDialogMessage(SYNCING_ERROR, ERROR_NETWORK);
        }

        @Override
        public void onResponse(Call call, String token, Object arg1, Response response) throws IOException {
            if (response.isSuccessful()) {
                Timber.e("upload media file info succeed!");
                try {
                    final JSONObject object = new JSONObject(response.body().string());
                    Timber.e("fetch media sync info from server:%s", object.toString());
                    if (object.getInt("status_code") != 0) {
                        updateDialogMessage(SYNCING_ERROR, object.getString("message"));
                        return;
                    }
                    final JSONObject item = object.getJSONObject("data");
                    updateDialogProgress(SYNCING_MEDIA, "", 10);
                    handleMediaSync(item, token);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                Timber.e("upload media file info error, code %d", response.code());
            }
        }
    });
}
Also used : Call(okhttp3.Call) OKHttpUtil(com.ichi2.utils.OKHttpUtil) JSONArray(com.ichi2.utils.JSONArray) IOException(java.io.IOException) SuppressLint(android.annotation.SuppressLint) FileNotFoundException(java.io.FileNotFoundException) SQLiteConstraintException(android.database.sqlite.SQLiteConstraintException) IOException(java.io.IOException) Response(okhttp3.Response) JSONObject(com.ichi2.utils.JSONObject) JSONObject(com.ichi2.utils.JSONObject) ZipFile(java.util.zip.ZipFile) File(java.io.File) RequestBody(okhttp3.RequestBody)

Aggregations

File (java.io.File)43 IOException (java.io.IOException)26 Collection (com.ichi2.libanki.Collection)25 JSONObject (com.ichi2.utils.JSONObject)19 ArrayList (java.util.ArrayList)17 Test (org.junit.Test)17 ZipFile (java.util.zip.ZipFile)14 JSONException (com.ichi2.utils.JSONException)10 FileOutputStream (java.io.FileOutputStream)10 List (java.util.List)10 JSONArray (com.ichi2.utils.JSONArray)9 FileInputStream (java.io.FileInputStream)9 FileNotFoundException (java.io.FileNotFoundException)9 SharedPreferences (android.content.SharedPreferences)8 JSONObject (org.json.JSONObject)8 Resources (android.content.res.Resources)7 Uri (android.net.Uri)7 RobolectricTest (com.ichi2.anki.RobolectricTest)7 ConfirmModSchemaException (com.ichi2.anki.exception.ConfirmModSchemaException)7 AnkiPackageImporter (com.ichi2.libanki.importer.AnkiPackageImporter)7