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;
}
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();
}
}
}
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();
}
}
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;
}
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());
}
}
});
}
Aggregations