0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 12:02:16 +02:00

Merge pull request #560 from timrae/syncErrorCatching

Sync error catching
This commit is contained in:
Nicolas Raoul 2014-10-22 00:05:20 +09:00
commit b0f1c96e85
9 changed files with 121 additions and 312 deletions

View File

@ -1558,7 +1558,7 @@ public class DeckPicker extends NavigationDrawerActivity implements OnShowcaseEv
} else if (resultType.equals("OutOfMemoryError")) {
dialogMessage = res.getString(R.string.error_insufficient_memory);
showSyncLogDialog(joinSyncMessages(dialogMessage, syncMessage));
} else if (resultType.equals("sanityCheckError")) {
} else if (resultType.equals("sanityCheckError") || resultType.equals("basicCheckFailed")) {
dialogMessage = res.getString(R.string.sync_sanity_failed);
showSyncErrorDialog(SyncErrorDialog.DIALOG_SYNC_SANITY_ERROR,
joinSyncMessages(dialogMessage, syncMessage));

View File

@ -0,0 +1,13 @@
package com.ichi2.anki.exception;
@SuppressWarnings("serial")
public class UnknownHttpResponseException extends Exception {
private Integer mCode;
public UnknownHttpResponseException(String message, Integer code) {
super(message);
mCode = code;
}
public int getResponseCode() {
return mCode;
}
}

View File

@ -274,12 +274,12 @@ public class AnkiStatsTaskHandler {
//only necessary on lower APIs because after honeycomb only one thread is used for all asynctasks
sLock.lock();
try {
if (!mIsRunning) {
Collection collection = (Collection) params[0];
if (!mIsRunning || collection == null || collection.getDb() == null) {
Log.d(AnkiDroidApp.TAG, "quiting CreateSmallTodayOverview before execution");
return null;
} else
Log.d(AnkiDroidApp.TAG, "starting CreateSmallTodayOverview" );
Collection collection = (Collection) params[0];
mTextView = (TextView) params[1];
//eventually put this in Stats (in desktop it is not though)
@ -341,12 +341,12 @@ public class AnkiStatsTaskHandler {
//only necessary on lower APIs because after honeycomb only one thread is used for all asynctasks
sLock.lock();
try {
if (!mIsRunning) {
Collection collection = (Collection) params[0];
if (!mIsRunning || collection == null || collection.getDb() == null) {
Log.d(AnkiDroidApp.TAG, "quiting CreateTodayLearnCountOnly before execution");
return null;
} else
Log.d(AnkiDroidApp.TAG, "starting CreateTodayLearnCountOnly" );
Collection collection = (Collection) params[0];
mViewPager = (ViewPager) params[1];
//eventually put this in Stats (in desktop it is not though)

View File

@ -32,6 +32,7 @@ import com.ichi2.anki.AnkiDb;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.Feedback;
import com.ichi2.anki.R;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.anki.exception.UnsupportedSyncException;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Decks;
@ -286,7 +287,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
return doInBackgroundDownloadMissingMedia(data);
case TASK_TYPE_UPGRADE_DECKS:
return doInBackgroundUpgradeDecks(data);
throw new RuntimeException("Upgrade decks no longer supported");
case TASK_TYPE_DOWNLOAD_SHARED_DECK:
return doInBackgroundDownloadSharedDeck(data);
@ -301,7 +302,14 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
String username = (String) data.data[0];
String password = (String) data.data[1];
HttpSyncer server = new RemoteServer(this, null);
HttpResponse ret = server.hostKey(username, password);
HttpResponse ret;
try {
ret = server.hostKey(username, password);
} catch (UnknownHttpResponseException e) {
data.success = false;
data.result = new Object[] { "error", e.getResponseCode(), e.getMessage() };
return data;
}
String hostkey = null;
boolean valid = false;
if (ret != null) {
@ -333,227 +341,18 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
}
private Payload doInBackgroundUpgradeDecks(Payload data) {
// Enable http request canceller
mCancelCallback = new CancelCallback();
String path = (String) data.data[0];
File ankiDir = new File(path);
if (!ankiDir.isDirectory()) {
data.success = false;
data.data = new Object[] { "wrong anki directory" };
return data;
}
// step 1: gather all .anki files into a zip, without media.
// we must store them as 1.anki, 2.anki and provide a map so we don't run into
// encoding issues with the zip file.
File[] fileList = ankiDir.listFiles(new OldAnkiDeckFilter());
List<String> corruptFiles = new ArrayList<String>();
JSONObject map = new JSONObject();
byte[] buf = new byte[1024];
String zipFilename = path + "/upload.zip";
String colFilename = path + AnkiDroidApp.COLLECTION_PATH;
try {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFilename));
int n = 1;
for (File f : fileList) {
String deckPath = f.getAbsolutePath();
// set journal mode to delete
try {
AnkiDb d = AnkiDatabaseManager.getDatabase(deckPath);
} catch (SQLiteDatabaseCorruptException e) {
// ignore invalid .anki files
corruptFiles.add(f.getName());
continue;
} finally {
AnkiDatabaseManager.closeDatabase(deckPath);
}
// zip file
String tmpName = n + ".anki";
FileInputStream in = new FileInputStream(deckPath);
ZipEntry ze = new ZipEntry(tmpName);
zos.putNextEntry(ze);
int len;
while ((len = in.read(buf)) >= 0) {
zos.write(buf, 0, len);
}
zos.closeEntry();
map.put(tmpName, f.getName());
n++;
}
// if all .anki files were found corrupted, abort
if (fileList.length == corruptFiles.size()) {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_failed) };
return data;
}
ZipEntry ze = new ZipEntry("map.json");
zos.putNextEntry(ze);
InputStream in = new ByteArrayInputStream(Utils.jsonToString(map).getBytes("UTF-8"));
int len;
while ((len = in.read(buf)) >= 0) {
zos.write(buf, 0, len);
}
zos.closeEntry();
zos.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (JSONException e) {
throw new RuntimeException(e);
}
File zipFile = new File(zipFilename);
// step 1.1: if it's over 50MB compressed, it must be upgraded by the user
if (zipFile.length() > 50 * 1024 * 1024) {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_exceeds) };
return data;
}
// step 2: upload zip file to upgrade service and get token
HttpSyncer h = new HttpSyncer(null, null);
// note: server doesn't expect it to be gzip compressed, because the zip file is compressed
// enable cancelling
publishProgress(R.string.upgrade_decks_upload, null, true);
try {
HttpResponse resp = h.req("upgrade/upload", new FileInputStream(zipFile), 0, null, mCancelCallback);
if (resp == null && !isCancelled()) {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_failed) };
return data;
}
String result;
String key = null;
if (!isCancelled()) {
result = h.stream2String(resp.getEntity().getContent());
if (result != null && result.startsWith("ok:")) {
key = result.split(":")[1];
} else {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_failed) };
return data;
}
}
while (!isCancelled()) {
result = h.stream2String(h.req("upgrade/status?key=" + key).getEntity().getContent());
if (result.equals("error")) {
data.success = false;
data.data = new Object[] { "error" };
return data;
} else if (result.startsWith("waiting:")) {
publishProgress(R.string.upgrade_decks_upload, result.split(":")[1]);
} else if (result.equals("upgrading")) {
publishProgress(new Object[] { R.string.upgrade_decks_upgrade_started });
} else if (result.equals("ready")) {
break;
} else {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_failed) };
return data;
}
Thread.sleep(1000);
}
// step 4: fetch upgraded file. this will return the .anki2 file directly, with
// gzip compression if the client says it can handle it
if (!isCancelled()) {
publishProgress(new Object[] { R.string.upgrade_decks_downloading });
resp = h.req("upgrade/download?key=" + key, null, 6, null, mCancelCallback);
// uploads/downloads have finished so disable cancelling
}
publishProgress(R.string.upgrade_decks_downloading, null, false);
if (isCancelled()) {
return null;
}
if (resp == null) {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_failed) };
return data;
}
// step 5: check the received file is valid
InputStream cont = resp.getEntity().getContent();
if (!h.writeToFile(cont, colFilename)) {
data.success = false;
data.data = new Object[] { sContext.getString(R.string.upgrade_deck_web_upgrade_sdcard, new File(
colFilename).length() / 1048576 + 1) };
(new File(colFilename)).delete();
return data;
}
// check the received file is ok
publishProgress(new Object[] { R.string.sync_check_download_file });
publishProgress(R.string.sync_check_download_file);
try {
AnkiDb d = AnkiDatabaseManager.getDatabase(colFilename);
if (!d.queryString("PRAGMA integrity_check").equalsIgnoreCase("ok")) {
data.success = false;
data.data = new Object[] { sContext.getResources() };
return data;
}
} finally {
AnkiDatabaseManager.closeDatabase(colFilename);
}
Collection col = AnkiDroidApp.openCollection(colFilename);
ArrayList<String> decks = col.getDecks().allNames(false);
ArrayList<String> failed = new ArrayList<String>();
ArrayList<File> mediaDirs = new ArrayList<File>();
for (File f : fileList) {
String name = f.getName().replaceFirst("\\.anki$", "");
if (!decks.contains(name)) {
failed.add(name);
} else {
mediaDirs.add(new File(f.getAbsolutePath().replaceFirst("\\.anki$", ".media")));
}
}
File newMediaDir = new File(col.getMedia().dir());
// step 6. move media files to new media directory
publishProgress(new Object[] { R.string.upgrade_decks_media });
ArrayList<String> failedMedia = new ArrayList<String>();
File curMediaDir = null;
for (File mediaDir : mediaDirs) {
curMediaDir = mediaDir;
// Check if media directory exists and is local
if (!curMediaDir.exists() || !curMediaDir.isDirectory()) {
// If not try finding it in dropbox 1.2.x
curMediaDir = new File(AnkiDroidApp.getDropboxDir(), mediaDir.getName());
if (!curMediaDir.exists() || !curMediaDir.isDirectory()) {
// No media for this deck
continue;
}
}
// Found media dir, copy files
for (File m : curMediaDir.listFiles()) {
try {
Utils.copyFile(m, new File(newMediaDir, m.getName()));
} catch (IOException e) {
failedMedia.add(curMediaDir.getName().replaceFirst("\\.media$", ".anki"));
break;
}
}
}
data.data = new Object[] { failed, failedMedia, newMediaDir.getAbsolutePath() };
data.success = true;
return data;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (IllegalStateException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
(new File(zipFilename)).delete();
}
}
private Payload doInBackgroundRegister(Payload data) {
String username = (String) data.data[0];
String password = (String) data.data[1];
HttpSyncer server = new RemoteServer(this, null);
HttpResponse ret = server.register(username, password);
HttpResponse ret;
try {
ret = server.register(username, password);
} catch (UnknownHttpResponseException e) {
data.success = false;
data.result = new Object[] { "error", e.getResponseCode(), e.getMessage() };
return data;
}
String hostkey = null;
boolean valid = false;
String status = null;
@ -744,6 +543,15 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
data.data = new Object[] { conflictResolution, col, dc[1], dc[2], mediaError };
return data;
}
} catch (UnknownHttpResponseException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundSync -- unknown response code error");
e.printStackTrace();
data.success = false;
Integer code = e.getResponseCode();
String msg = e.getLocalizedMessage();
data.result = new Object[] { "error", code , msg };
data.data = new Object[] { mediaUsn };
return data;
} catch (Exception e) {
// Global error catcher.
// Try to give a human readable error, otherwise print the raw error message

View File

@ -23,6 +23,7 @@ import com.ichi2.anki.AnkiDatabaseManager;
import com.ichi2.anki.AnkiDb;
import com.ichi2.anki.AnkiDroidApp;
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;
@ -62,7 +63,7 @@ public class FullSyncer extends HttpSyncer {
@Override
public Object[] download() {
public Object[] download() throws UnknownHttpResponseException {
InputStream cont;
try {
HttpResponse ret = super.req("download");
@ -118,7 +119,7 @@ public class FullSyncer extends HttpSyncer {
@Override
public Object[] upload() {
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")) {

View File

@ -1,6 +1,7 @@
/***************************************************************************************
* Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com> *
* Copyright (c) 2012 Kostas Spyropoulos <inigo.aldana@gmail.com> *
* Copyright (c) 2014 Timothy Rae <perceptualchaos2@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 *
@ -20,6 +21,7 @@ package com.ichi2.libanki.sync;
import android.util.Log;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.async.Connection;
import com.ichi2.libanki.Consts;
import com.ichi2.libanki.Utils;
@ -103,33 +105,46 @@ public class HttpSyncer {
}
public HttpResponse req(String method) {
public void assertOk(HttpResponse resp) throws UnknownHttpResponseException {
// Throw RuntimeException if HTTP error
if (resp == null) {
throw new UnknownHttpResponseException("Null HttpResponse", -2);
}
int resultCode = resp.getStatusLine().getStatusCode();
if (!(resultCode == 200 || resultCode == 403)) {
String reason = resp.getStatusLine().getReasonPhrase();
throw new UnknownHttpResponseException(reason, resultCode);
}
}
public HttpResponse req(String method) throws UnknownHttpResponseException {
return req(method, null);
}
public HttpResponse req(String method, InputStream fobj) {
public HttpResponse req(String method, InputStream fobj) throws UnknownHttpResponseException {
return req(method, fobj, 6);
}
public HttpResponse req(String method, int comp, InputStream fobj) {
public HttpResponse req(String method, int comp, InputStream fobj) throws UnknownHttpResponseException {
return req(method, fobj, comp);
}
public HttpResponse req(String method, InputStream fobj, int comp) {
public HttpResponse req(String method, InputStream fobj, int comp) throws UnknownHttpResponseException {
return req(method, fobj, comp, null);
}
public HttpResponse req(String method, InputStream fobj, int comp, JSONObject registerData) {
return req(method, fobj, comp, registerData, null);
public HttpResponse req(String method, InputStream fobj, int comp, JSONObject registerData) throws UnknownHttpResponseException {
return req(method, fobj, comp, registerData, null) ;
}
public HttpResponse req(String method, InputStream fobj, int comp, JSONObject registerData,
Connection.CancelCallback cancelCallback) {
Connection.CancelCallback cancelCallback) throws UnknownHttpResponseException {
File tmpFileBuffer = null;
try {
String bdry = "--" + BOUNDARY;
@ -212,10 +227,14 @@ public class HttpSyncer {
try {
HttpClient httpClient = new DefaultHttpClient(cm, params);
return httpClient.execute(httpPost);
HttpResponse httpResponse = httpClient.execute(httpPost);
// we assume badAuthRaises flag from Anki Desktop always False
// so just throw new RuntimeException if response code not 200 or 403
assertOk(httpResponse);
return httpResponse;
} catch (SSLException e) {
Log.e(AnkiDroidApp.TAG, "SSLException while building HttpClient", e);
return null;
throw new RuntimeException("SSLException while building HttpClient");
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
@ -300,57 +319,57 @@ public class HttpSyncer {
}
public HttpResponse hostKey(String arg1, String arg2) {
public HttpResponse hostKey(String arg1, String arg2) throws UnknownHttpResponseException {
return null;
}
public JSONObject applyChanges(JSONObject kw) {
public JSONObject applyChanges(JSONObject kw) throws UnknownHttpResponseException {
return null;
}
public JSONObject start(JSONObject kw) {
public JSONObject start(JSONObject kw) throws UnknownHttpResponseException {
return null;
}
public JSONObject chunk(JSONObject kw) {
public JSONObject chunk(JSONObject kw) throws UnknownHttpResponseException {
return null;
}
public JSONObject chunk() {
public JSONObject chunk() throws UnknownHttpResponseException {
return null;
}
public long finish() {
public long finish() throws UnknownHttpResponseException {
return 0;
}
public HttpResponse meta() {
public HttpResponse meta() throws UnknownHttpResponseException {
return null;
}
public Object[] download() {
public Object[] download() throws UnknownHttpResponseException {
return null;
}
public Object[] upload() {
public Object[] upload() throws UnknownHttpResponseException {
return null;
}
public JSONObject sanityCheck2(JSONObject client) {
public JSONObject sanityCheck2(JSONObject client) throws UnknownHttpResponseException {
return null;
}
public void applyChunk(JSONObject sech) {
public void applyChunk(JSONObject sech) throws UnknownHttpResponseException {
}
public class ProgressByteEntity extends AbstractHttpEntity {
@ -422,7 +441,7 @@ public class HttpSyncer {
}
public HttpResponse register(String user, String pw) {
public HttpResponse register(String user, String pw) throws UnknownHttpResponseException {
return null;
}
@ -431,3 +450,5 @@ public class HttpSyncer {
return Consts.SYNC_BASE + "sync/";
}
}

View File

@ -21,6 +21,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.async.Connection;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Consts;
@ -56,7 +57,7 @@ public class RemoteMediaServer extends HttpSyncer {
}
public JSONObject begin() {
public JSONObject begin() throws UnknownHttpResponseException {
try {
mPostVars = new HashMap<String, Object>();
mPostVars.put("k", mHKey);
@ -77,7 +78,7 @@ public class RemoteMediaServer extends HttpSyncer {
// args: lastUsn
public JSONArray mediaChanges(int lastUsn) {
public JSONArray mediaChanges(int lastUsn) throws UnknownHttpResponseException {
try {
mPostVars = new HashMap<String, Object>();
mPostVars.put("sk", mSKey);
@ -95,7 +96,7 @@ public class RemoteMediaServer extends HttpSyncer {
// args: files
public ZipFile downloadFiles(List<String> top) {
public ZipFile downloadFiles(List<String> top) throws UnknownHttpResponseException {
try {
HttpResponse resp;
resp = super.req("downloadFiles",
@ -113,7 +114,7 @@ public class RemoteMediaServer extends HttpSyncer {
}
public JSONArray uploadChanges(File zip) {
public JSONArray uploadChanges(File zip) throws UnknownHttpResponseException {
try {
// no compression, as we compress the zip file instead
HttpResponse resp = super.req("uploadChanges", new FileInputStream(zip), 0);
@ -128,7 +129,7 @@ public class RemoteMediaServer extends HttpSyncer {
// args: local
public String mediaSanity(int lcnt) {
public String mediaSanity(int lcnt) throws UnknownHttpResponseException {
try {
HttpResponse resp = super.req("mediaSanity",
super.getInputStream(Utils.jsonToString(new JSONObject().put("local", lcnt))));

View File

@ -1,5 +1,6 @@
/***************************************************************************************
* Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com> *
* Copyright (c) 2014 Timothy Rae <perceptualchaos2@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 *
@ -17,6 +18,7 @@
package com.ichi2.libanki.sync;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.async.Connection;
import com.ichi2.libanki.Consts;
import com.ichi2.libanki.Utils;
@ -38,9 +40,10 @@ public class RemoteServer extends HttpSyncer {
}
/** Returns hkey or none if user/pw incorrect. */
/** Returns hkey or none if user/pw incorrect.
* @throws UnknownHttpResponseException */
@Override
public HttpResponse hostKey(String user, String pw) {
public HttpResponse hostKey(String user, String pw) throws UnknownHttpResponseException {
try {
mPostVars = new HashMap<String, Object>();
JSONObject jo = new JSONObject();
@ -54,7 +57,7 @@ public class RemoteServer extends HttpSyncer {
@Override
public HttpResponse register(String user, String pw) {
public HttpResponse register(String user, String pw) throws UnknownHttpResponseException {
try {
JSONObject jo = new JSONObject();
jo.put("u", URLEncoder.encode(user, "UTF-8"));
@ -69,7 +72,7 @@ public class RemoteServer extends HttpSyncer {
@Override
public HttpResponse meta() {
public HttpResponse meta() throws UnknownHttpResponseException {
try {
mPostVars = new HashMap<String, Object>();
mPostVars.put("k", mHKey);
@ -86,43 +89,40 @@ public class RemoteServer extends HttpSyncer {
@Override
public JSONObject applyChanges(JSONObject kw) {
public JSONObject applyChanges(JSONObject kw) throws UnknownHttpResponseException {
return _run("applyChanges", kw);
}
@Override
public JSONObject start(JSONObject kw) {
public JSONObject start(JSONObject kw) throws UnknownHttpResponseException {
return _run("start", kw);
}
@Override
public JSONObject chunk() {
public JSONObject chunk() throws UnknownHttpResponseException {
JSONObject co = new JSONObject();
return _run("chunk", co);
}
@Override
public void applyChunk(JSONObject sech) {
public void applyChunk(JSONObject sech) throws UnknownHttpResponseException {
_run("applyChunk", sech);
}
@Override
public JSONObject sanityCheck2(JSONObject client) {
public JSONObject sanityCheck2(JSONObject client) throws UnknownHttpResponseException {
return _run("sanityCheck2", client);
}
@Override
public long finish() {
public long finish() throws UnknownHttpResponseException {
try {
HttpResponse ret = super.req("finish", super.getInputStream("{}"));
if (ret == null) {
return 0;
}
String s = super.stream2String(ret.getEntity().getContent());
return Long.parseLong(s);
} catch (NumberFormatException e) {
@ -135,24 +135,11 @@ public class RemoteServer extends HttpSyncer {
}
private JSONObject _run(String cmd, JSONObject data) {
private JSONObject _run(String cmd, JSONObject data) throws UnknownHttpResponseException {
HttpResponse ret = super.req(cmd, super.getInputStream(Utils.jsonToString(data)));
if (ret == null) {
return null;
}
String s = "";
try {
int resultType = ret.getStatusLine().getStatusCode();
if (resultType == 200) {
s = super.stream2String(ret.getEntity().getContent());
if (!s.equalsIgnoreCase("null") && s.length() != 0) {
return new JSONObject(s);
}
}
JSONObject o = new JSONObject();
o.put("errorType", resultType);
o.put("errorReason", s.equals("null") ? "null result (" + cmd + ")" : ret.getStatusLine().getReasonPhrase());
return o;
String s = super.stream2String(ret.getEntity().getContent());
return new JSONObject(s);
} catch (IllegalStateException e) {
throw new RuntimeException(e);
} catch (IOException e) {

View File

@ -1,5 +1,6 @@
/***************************************************************************************
* Copyright (c) 2011 Norbert Nagold <norbert.nagold@gmail.com> *
* Copyright (c) 2014 Timothy Rae <perceptualchaos2@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 *
@ -22,6 +23,7 @@ import android.util.Log;
import com.ichi2.anki.AnkiDroidApp;
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;
@ -74,12 +76,12 @@ public class Syncer {
/** Returns 'noChanges', 'fullSync', 'success', etc */
public Object[] sync() {
public Object[] sync() throws UnknownHttpResponseException {
return sync(null);
}
public Object[] sync(Connection con) {
public Object[] sync(Connection con) throws UnknownHttpResponseException {
mSyncMsg = "";
// if the deck has any pending changes, flush them first and bump mod time
mCol.save();
@ -91,8 +93,6 @@ public class Syncer {
int returntype = ret.getStatusLine().getStatusCode();
if (returntype == 403) {
return new Object[] { "badAuth" };
} else if (returntype != 200) {
return new Object[] { "error", returntype, ret.getStatusLine().getReasonPhrase() };
}
try {
mCol.getDb().getDatabase().beginTransaction();
@ -137,8 +137,11 @@ public class Syncer {
return new Object[] { "fullSync" };
}
mLNewer = mLMod > mRMod;
// TODO: step 1.5 appears to be missing here
// step 1.5: check collection is valid
if (!mCol.basicCheck()) {
mCol.log("basic check");
return new Object[] { "basicCheckFailed" };
}
// step 2: deletions
publishProgress(con, R.string.sync_deletions_message);
@ -151,15 +154,6 @@ public class Syncer {
Log.i(AnkiDroidApp.TAG, "Sync: sending and receiving removed data");
JSONObject rrem = mServer.start(o);
if (rrem == null) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return null;
}
if (rrem.has("errorType")) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return new Object[] { "error", rrem.get("errorType"), rrem.get("errorReason") };
}
Log.i(AnkiDroidApp.TAG, "Sync: applying removed data");
remove(rrem);
// ... and small objects
@ -172,14 +166,6 @@ public class Syncer {
Log.i(AnkiDroidApp.TAG, "Sync: sending and receiving small changes");
JSONObject rchg = mServer.applyChanges(sch);
if (rchg == null) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return null;
}
if (rchg.has("errorType")) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return new Object[] { "error", rchg.get("errorType"), rchg.get("errorReason") };
}
Log.i(AnkiDroidApp.TAG, "Sync: merging small changes");
mergeChanges(lchg, rchg);
@ -189,14 +175,6 @@ public class Syncer {
Log.i(AnkiDroidApp.TAG, "Sync: downloading chunked data");
JSONObject chunk = mServer.chunk();
mCol.log("server chunk", chunk);
if (chunk == null) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return null;
}
if (chunk.has("errorType")) {
Log.i(AnkiDroidApp.TAG, "Sync: error - returning");
return new Object[] { "error", chunk.get("errorType"), chunk.get("errorReason") };
}
Log.i(AnkiDroidApp.TAG, "Sync: applying chunked data");
applyChunk(chunk);
if (chunk.getBoolean("done")) {