/***************************************************************************************
* Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.libanki.sync;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.net.Uri;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.CollectionHelper;
import com.ichi2.anki.R;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.async.Connection;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Consts;
import com.ichi2.libanki.DB;
import com.ichi2.libanki.Utils;
import com.ichi2.utils.VersionUtils;
import org.apache.http.HttpResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import timber.log.Timber;
public class FullSyncer extends HttpSyncer {
Collection mCol;
Connection mCon;
public FullSyncer(Collection col, String hkey, Connection con) {
super(hkey, con);
mPostVars = new HashMap<>();
mPostVars.put("k", hkey);
mPostVars.put("v",
String.format(Locale.US, "ankidroid,%s,%s", VersionUtils.getPkgVersionName(), Utils.platDesc()));
mCol = col;
mCon = con;
}
@Override
public String syncURL() {
// Allow user to specify custom sync server
SharedPreferences userPreferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance());
if (userPreferences!= null && userPreferences.getBoolean("useCustomSyncServer", false)) {
Uri syncBase = Uri.parse(userPreferences.getString("syncBaseUrl", Consts.SYNC_BASE));
return syncBase.buildUpon().appendPath("sync").toString() + "/";
}
// Usual case
return Consts.SYNC_BASE + "sync/";
}
@Override
public Object[] download() throws UnknownHttpResponseException {
InputStream cont;
try {
HttpResponse ret = super.req("download");
if (ret == null) {
return null;
}
cont = ret.getEntity().getContent();
} catch (IllegalStateException e1) {
throw new RuntimeException(e1);
} catch (IOException e1) {
return null;
}
String path;
if (mCol != null) {
// 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);
FileInputStream fis = new FileInputStream(tpath);
if (super.stream2String(fis, 15).equals("upgradeRequired")) {
return new Object[]{"upgradeRequired"};
}
} 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 new Object[] { "sdAccessError" };
}
// check the received file is ok
mCon.publishProgress(R.string.sync_check_download_file);
DB tempDb = null;
try {
tempDb = new DB(tpath);
if (!tempDb.queryString("PRAGMA integrity_check").equalsIgnoreCase("ok")) {
Timber.e("Full sync - downloaded file corrupt");
return new Object[] { "remoteDbError" };
}
} catch (SQLiteDatabaseCorruptException e) {
Timber.e("Full sync - downloaded file corrupt");
return new Object[] { "remoteDbError" };
} finally {
if (tempDb != null) {
tempDb.close();
}
}
// overwrite existing collection
File newFile = new File(tpath);
if (newFile.renameTo(new File(path))) {
return new Object[] { "success" };
} else {
return new Object[] { "overwriteError" };
}
}
@Override
public Object[] upload() throws UnknownHttpResponseException {
// make sure it's ok before we try to upload
mCon.publishProgress(R.string.sync_check_upload_file);
if (!mCol.getDb().queryString("PRAGMA integrity_check").equalsIgnoreCase("ok")) {
return new Object[] { "dbError" };
}
if (!mCol.basicCheck()) {
return new Object[] { "dbError" };
}
// apply some adjustments, then upload
mCol.beforeUpload();
String filePath = mCol.getPath();
HttpResponse ret;
mCon.publishProgress(R.string.sync_uploading_message);
try {
ret = super.req("upload", new FileInputStream(filePath));
if (ret == null) {
return null;
}
int status = ret.getStatusLine().getStatusCode();
if (status != 200) {
// error occurred
return new Object[] { "error", status, ret.getStatusLine().getReasonPhrase() };
} else {
return new Object[] { super.stream2String(ret.getEntity().getContent()) };
}
} catch (IllegalStateException | IOException e) {
throw new RuntimeException(e);
}
}
}