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:
commit
b0f1c96e85
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")) {
|
||||
|
@ -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/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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))));
|
||||
|
@ -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) {
|
||||
|
@ -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")) {
|
||||
|
Loading…
Reference in New Issue
Block a user