use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class Media method rebuildIfInvalid.
public void rebuildIfInvalid() throws IOException {
try {
_changed();
return;
} catch (Exception e) {
if (!ExceptionUtil.containsMessage(e, "no such table: meta")) {
throw e;
}
AnkiDroidApp.sendExceptionReport(e, "media::rebuildIfInvalid");
// TODO: We don't know the root cause of the missing meta table
Timber.w(e, "Error accessing media database. Rebuilding");
// continue below
}
// Delete and recreate the file
mDb.getDatabase().close();
String path = mDb.getPath();
Timber.i("Deleted %s", path);
new File(path).delete();
mDb = new DB(path);
_initDB();
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
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 directory 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"));
List<String> fnames = new ArrayList<>();
try (ZipOutputStream z = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
Cursor cur = mDb.query("select fname, csum from media where dirty=1 limit " + Consts.SYNC_MAX_FILES)) {
z.setMethod(ZipOutputStream.DEFLATED);
// 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];
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) {
Timber.w(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_MAX_BYTES) {
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);
}
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class Media method maybeUpgrade.
public void maybeUpgrade() {
String oldpath = dir() + ".db";
File oldDbFile = new File(oldpath);
if (oldDbFile.exists()) {
mDb.execute(String.format(Locale.US, "attach \"%s\" as old", oldpath));
try {
String sql = "insert into media\n" + " select m.fname, csum, mod, ifnull((select 1 from log l2 where l2.fname=m.fname), 0) as dirty\n" + " from old.media m\n" + " left outer join old.log l using (fname)\n" + " union\n" + " select fname, null, 0, 1 from old.log where type=" + Consts.CARD_TYPE_LRN + ";";
mDb.execute(sql);
mDb.execute("delete from meta");
mDb.execute("insert into meta select dirMod, usn from old.meta");
mDb.commit();
} catch (Exception e) {
// if we couldn't import the old db for some reason, just start anew
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
mCol.log("failed to import old media db:" + sw.toString());
}
mDb.execute("detach old");
File newDbFile = new File(oldpath + ".old");
if (newDbFile.exists()) {
newDbFile.delete();
}
oldDbFile.renameTo(newDbFile);
}
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class Storage method getDatabaseVersion.
/**
* Helper method for when the collection can't be opened
*/
public static int getDatabaseVersion(String path) throws UnknownDatabaseVersionException {
try {
if (!new File(path).exists()) {
throw new UnknownDatabaseVersionException(new FileNotFoundException(path));
}
DB db = new DB(path);
int result = db.queryScalar("SELECT ver FROM col");
db.close();
return result;
} catch (Exception e) {
Timber.w(e, "Can't open database");
throw new UnknownDatabaseVersionException(e);
}
}
use of com.ichi2.libanki.DB in project Anki-Android by ankidroid.
the class FullSyncer method download.
@NonNull
public ConnectionResultType download() throws UnknownHttpResponseException {
InputStream cont;
ResponseBody body = null;
try {
Response ret = super.req("download");
if (ret == null || ret.body() == null) {
return null;
}
body = ret.body();
cont = body.byteStream();
} catch (IllegalArgumentException e1) {
if (body != null) {
body.close();
}
throw new RuntimeException(e1);
}
String path;
if (mCol != null) {
Timber.i("Closing collection for full sync");
// Usual case where collection is non-null
path = mCol.getPath();
mCol.close();
mCol = null;
} else {
// Allow for case where collection is completely unreadable
Timber.w("Collection was unexpectedly null when doing full sync download");
path = CollectionHelper.getCollectionPath(AnkiDroidApp.getInstance());
}
String tpath = path + ".tmp";
try {
super.writeToFile(cont, tpath);
Timber.d("Full Sync - Downloaded temp file");
FileInputStream fis = new FileInputStream(tpath);
if ("upgradeRequired".equals(super.stream2String(fis, 15))) {
Timber.w("Full Sync - 'Upgrade Required' message received");
return UPGRADE_REQUIRED;
}
} catch (FileNotFoundException e) {
Timber.e(e, "Failed to create temp file when downloading collection.");
throw new RuntimeException(e);
} catch (IOException e) {
Timber.e(e, "Full sync failed to download collection.");
return SD_ACCESS_ERROR;
} finally {
body.close();
}
// check the received file is ok
mCon.publishProgress(R.string.sync_check_download_file);
DB tempDb = null;
try {
tempDb = new DB(tpath);
if (!"ok".equalsIgnoreCase(tempDb.queryString("PRAGMA integrity_check"))) {
Timber.e("Full sync - downloaded file corrupt");
return REMOTE_DB_ERROR;
}
} catch (SQLiteDatabaseCorruptException e) {
Timber.e("Full sync - downloaded file corrupt");
return REMOTE_DB_ERROR;
} finally {
if (tempDb != null) {
tempDb.close();
}
}
Timber.d("Full Sync: Downloaded file was not corrupt");
// overwrite existing collection
File newFile = new File(tpath);
if (newFile.renameTo(new File(path))) {
Timber.i("Full Sync Success: Overwritten collection with downloaded file");
return SUCCESS;
} else {
Timber.w("Full Sync: Error overwriting collection with downloaded file");
return OVERWRITE_ERROR;
}
}
Aggregations