Search in sources :

Example 1 with UnknownHttpResponseException

use of com.ichi2.anki.exception.UnknownHttpResponseException in project AnkiChinaAndroid by ankichinateam.

the class Connection method doInBackgroundLogin.

private Payload doInBackgroundLogin(Payload data) {
    String username = (String) data.data[0];
    String password = (String) data.data[1];
    HostNum hostNum = (HostNum) data.data[2];
    HttpSyncer server = new RemoteServer(this, null, hostNum);
    Response ret;
    try {
        ret = server.hostKey(username, password);
    } catch (UnknownHttpResponseException e) {
        data.success = false;
        data.result = new Object[] { "error", e.getResponseCode(), e.getMessage() };
        return data;
    } catch (Exception e2) {
        // Ask user to report all bugs which aren't timeout errors
        if (!timeoutOccurred(e2)) {
            AnkiDroidApp.sendExceptionReport(e2, "doInBackgroundLogin");
        }
        data.success = false;
        data.result = new Object[] { "connectionError", e2 };
        return data;
    }
    String hostkey = null;
    boolean valid = false;
    if (ret != null) {
        data.returnType = ret.code();
        Timber.d("doInBackgroundLogin - response from server: %d, (%s)", data.returnType, ret.message());
        if (data.returnType == 200) {
            try {
                JSONObject response = new JSONObject(ret.body().string());
                hostkey = response.getString("key");
                valid = (hostkey != null) && (hostkey.length() > 0);
            } catch (JSONException e) {
                valid = false;
            } catch (IllegalStateException | IOException | NullPointerException e) {
                throw new RuntimeException(e);
            }
        }
    } else {
        Timber.e("doInBackgroundLogin - empty response from server");
    }
    if (valid) {
        data.success = true;
        data.data = new String[] { username, hostkey };
    } else {
        data.success = false;
    }
    return data;
}
Also used : JSONException(com.ichi2.utils.JSONException) HostNum(com.ichi2.libanki.sync.HostNum) IOException(java.io.IOException) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) JSONException(com.ichi2.utils.JSONException) NoEnoughServerSpaceException(com.ichi2.anki.exception.NoEnoughServerSpaceException) MediaSyncException(com.ichi2.anki.exception.MediaSyncException) IOException(java.io.IOException) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) Response(okhttp3.Response) JSONObject(com.ichi2.utils.JSONObject) JSONObject(com.ichi2.utils.JSONObject) HttpSyncer(com.ichi2.libanki.sync.HttpSyncer) RemoteServer(com.ichi2.libanki.sync.RemoteServer)

Example 2 with UnknownHttpResponseException

use of com.ichi2.anki.exception.UnknownHttpResponseException in project AnkiChinaAndroid by ankichinateam.

the class MediaSyncer method sync.

public String sync(long restSpace) throws UnknownHttpResponseException, MediaSyncException, NoEnoughServerSpaceException {
    // of this class about this difference to the original.
    if (mCol.getMedia().needScan()) {
        mCon.publishProgress(R.string.sync_media_find);
        mCol.log("findChanges");
        try {
            mCol.getMedia().findChanges();
        } catch (SQLException ignored) {
            return "corruptMediaDB";
        }
    }
    // begin session and check if in sync
    int lastUsn = mCol.getMedia().lastUsn();
    JSONObject ret = mServer.begin();
    int srvUsn = ret.getInt("usn");
    if ((lastUsn == srvUsn) && !(mCol.getMedia().haveDirty())) {
        return "noChanges";
    }
    // loop through and process changes from server
    mCol.log("last local usn is " + lastUsn);
    mDownloadCount = 0;
    while (true) {
        // Allow cancellation (note: media sync has no finish command, so just throw)
        if (Connection.getIsCancelled()) {
            Timber.i("Sync was cancelled");
            throw new RuntimeException("UserAbortedSync");
        }
        JSONArray data = mServer.mediaChanges(lastUsn);
        mCol.log("mediaChanges resp count: ", data.length());
        if (data.length() == 0) {
            break;
        }
        List<String> need = new ArrayList<>();
        lastUsn = data.getJSONArray(data.length() - 1).getInt(1);
        for (int i = 0; i < data.length(); i++) {
            // Allow cancellation (note: media sync has no finish command, so just throw)
            if (Connection.getIsCancelled()) {
                Timber.i("Sync was cancelled");
                throw new RuntimeException("UserAbortedSync");
            }
            String fname = data.getJSONArray(i).getString(0);
            int rusn = data.getJSONArray(i).getInt(1);
            String rsum = null;
            if (!data.getJSONArray(i).isNull(2)) {
                // If `rsum` is a JSON `null` value, `.optString(2)` will
                // return `"null"` as a string
                rsum = data.getJSONArray(i).optString(2);
            }
            Pair<String, Integer> info = mCol.getMedia().syncInfo(fname);
            String lsum = info.first;
            int ldirty = info.second;
            mCol.log(String.format(Locale.US, "check: lsum=%s rsum=%s ldirty=%d rusn=%d fname=%s", TextUtils.isEmpty(lsum) ? "" : lsum.subSequence(0, 4), TextUtils.isEmpty(rsum) ? "" : rsum.subSequence(0, 4), ldirty, rusn, fname));
            if (!TextUtils.isEmpty(rsum)) {
                // added/changed remotely
                if (TextUtils.isEmpty(lsum) || !lsum.equals(rsum)) {
                    mCol.log("will fetch");
                    need.add(fname);
                } else {
                    mCol.log("have same already");
                }
                mCol.getMedia().markClean(Collections.singletonList(fname));
            } else if (!TextUtils.isEmpty(lsum)) {
                // deleted remotely
                if (ldirty == 0) {
                    mCol.log("delete local");
                    mCol.getMedia().syncDelete(fname);
                } else {
                    // conflict: local add overrides remote delete
                    mCol.log("conflict; will send");
                }
            } else {
                // deleted both sides
                mCol.log("both sides deleted");
                mCol.getMedia().markClean(Collections.singletonList(fname));
            }
        }
        _downloadFiles(need);
        mCol.log("update last usn to " + lastUsn);
        // commits
        mCol.getMedia().setLastUsn(lastUsn);
    }
    // at this point, we're all up to date with the server's changes,
    // and we need to send our own
    boolean updateConflict = false;
    int toSend = mCol.getMedia().dirtyCount();
    long needSize = mCol.getMedia().getMediaSizeNeededUpload();
    if (needSize >= 0 && restSpace < needSize && Consts.loginAnkiChina()) {
        Timber.d("No Enough Server Space Exception,rest is:" + restSpace + ",need size:" + needSize);
        throw new NoEnoughServerSpaceException(restSpace, needSize);
    }
    while (true) {
        Pair<File, List<String>> changesZip = mCol.getMedia().mediaChangesZip();
        File zip = changesZip.first;
        try {
            List<String> fnames = changesZip.second;
            if (fnames.size() == 0) {
                break;
            }
            mCon.publishProgress(String.format(AnkiDroidApp.getAppResources().getString(R.string.sync_media_changes_count), toSend));
            JSONArray changes = mServer.uploadChanges(zip);
            int processedCnt = changes.getInt(0);
            int serverLastUsn = changes.getInt(1);
            mCol.getMedia().markClean(fnames.subList(0, processedCnt));
            mCol.log(String.format(Locale.US, "processed %d, serverUsn %d, clientUsn %d", processedCnt, serverLastUsn, lastUsn));
            if (serverLastUsn - processedCnt == lastUsn) {
                mCol.log("lastUsn in sync, updating local");
                lastUsn = serverLastUsn;
                // commits
                mCol.getMedia().setLastUsn(serverLastUsn);
            } else {
                mCol.log("concurrent update, skipping usn update");
                // commit for markClean
                mCol.getMedia().getDb().commit();
                updateConflict = true;
            }
            toSend -= processedCnt;
        } finally {
            zip.delete();
        }
    }
    if (updateConflict) {
        mCol.log("restart sync due to concurrent update");
        return sync(restSpace);
    }
    int lcnt = mCol.getMedia().mediacount();
    String sRet = mServer.mediaSanity(lcnt);
    if ("OK".equals(sRet)) {
        return "OK";
    } else {
        mCol.getMedia().forceResync();
        return sRet;
    }
}
Also used : SQLException(android.database.SQLException) JSONArray(com.ichi2.utils.JSONArray) ArrayList(java.util.ArrayList) JSONObject(com.ichi2.utils.JSONObject) ArrayList(java.util.ArrayList) List(java.util.List) File(java.io.File) ZipFile(java.util.zip.ZipFile) NoEnoughServerSpaceException(com.ichi2.anki.exception.NoEnoughServerSpaceException)

Example 3 with UnknownHttpResponseException

use of com.ichi2.anki.exception.UnknownHttpResponseException in project AnkiChinaAndroid by ankichinateam.

the class RemoteMediaServer method mediaSanity.

// args: local
public String mediaSanity(int lcnt) throws UnknownHttpResponseException, MediaSyncException {
    try {
        Response resp = super.req("mediaSanity", HttpSyncer.getInputStream(Utils.jsonToString(new JSONObject().put("local", lcnt))));
        JSONObject jresp = new JSONObject(resp.body().string());
        return _dataOnly(jresp, String.class);
    } catch (IOException | NullPointerException e) {
        throw new RuntimeException(e);
    }
}
Also used : Response(okhttp3.Response) JSONObject(com.ichi2.utils.JSONObject) IOException(java.io.IOException)

Example 4 with UnknownHttpResponseException

use of com.ichi2.anki.exception.UnknownHttpResponseException in project AnkiChinaAndroid by ankichinateam.

the class RemoteMediaServer method uploadChanges.

public JSONArray uploadChanges(File zip) throws UnknownHttpResponseException, MediaSyncException {
    try {
        // no compression, as we compress the zip file instead
        Response resp = super.req("uploadChanges", new FileInputStream(zip), 0);
        JSONObject jresp = new JSONObject(resp.body().string());
        return _dataOnly(jresp, JSONArray.class);
    } catch (IOException | NullPointerException e) {
        throw new RuntimeException(e);
    }
}
Also used : Response(okhttp3.Response) JSONObject(com.ichi2.utils.JSONObject) IOException(java.io.IOException) FileInputStream(java.io.FileInputStream)

Example 5 with UnknownHttpResponseException

use of com.ichi2.anki.exception.UnknownHttpResponseException in project AnkiChinaAndroid by ankichinateam.

the class Syncer method sync.

/**
 * Returns 'noChanges', 'fullSync', 'success', etc
 */
// public Object[] sync() throws UnknownHttpResponseException {
// return sync(null);
// }
public Object[] sync(Connection con, long restSpace) throws UnknownHttpResponseException, NoEnoughServerSpaceException {
    mSyncMsg = "";
    setRestSpace(restSpace);
    // if the deck has any pending changes, flush them first and bump mod time
    mCol.getSched()._updateCutoff();
    mCol.save();
    // step 1: login & metadata
    Response ret = mServer.meta();
    if (ret == null) {
        return null;
    }
    int returntype = ret.code();
    if (returntype == 403) {
        return new Object[] { "badAuth" };
    }
    try {
        mCol.getDb().getDatabase().beginTransaction();
        try {
            Timber.i("Sync: getting meta data from server");
            JSONObject rMeta = new JSONObject(ret.body().string());
            mCol.log("rmeta", rMeta);
            mSyncMsg = rMeta.getString("msg");
            if (!rMeta.getBoolean("cont")) {
                // Don't add syncMsg; it can be fetched by UI code using the accessor
                return new Object[] { "serverAbort" };
            } else {
            // don't abort, but ui should show messages after sync finishes
            // and require confirmation if it's non-empty
            }
            throwExceptionIfCancelled(con);
            long rscm = rMeta.getLong("scm");
            int rts = rMeta.getInt("ts");
            mRMod = rMeta.getLong("mod");
            mMaxUsn = rMeta.getInt("usn");
            // skip uname, AnkiDroid already stores and shows it
            trySetHostNum(rMeta);
            Timber.i("Sync: building local meta data");
            JSONObject lMeta = meta();
            mCol.log("lmeta", lMeta);
            mLMod = lMeta.getLong("mod");
            mMinUsn = lMeta.getInt("usn");
            long lscm = lMeta.getLong("scm");
            int lts = lMeta.getInt("ts");
            long diff = Math.abs(rts - lts);
            if (diff > 300) {
                mCol.log("clock off");
                return new Object[] { "clockOff", diff };
            }
            if (mLMod == mRMod) {
                Timber.i("Sync: no changes - returning");
                mCol.log("no changes");
                return new Object[] { "noChanges" };
            } else if (lscm != rscm) {
                Timber.i("Sync: full sync necessary - returning");
                mCol.log("schema diff");
                return new Object[] { "fullSync" };
            }
            mLNewer = mLMod > mRMod;
            // step 1.5: check collection is valid
            if (!mCol.basicCheck()) {
                mCol.log("basic check");
                return new Object[] { "basicCheckFailed" };
            }
            throwExceptionIfCancelled(con);
            // step 2: deletions
            publishProgress(con, R.string.sync_deletions_message);
            Timber.i("Sync: collection removed data");
            JSONObject lrem = removed();
            JSONObject o = new JSONObject();
            o.put("minUsn", mMinUsn);
            o.put("lnewer", mLNewer);
            o.put("graves", lrem);
            Timber.i("Sync: sending and receiving removed data");
            JSONObject rrem = mServer.start(o);
            Timber.i("Sync: applying removed data");
            throwExceptionIfCancelled(con);
            remove(rrem);
            // ... and small objects
            publishProgress(con, R.string.sync_small_objects_message);
            Timber.i("Sync: collection small changes");
            JSONObject lchg = changes();
            JSONObject sch = new JSONObject();
            sch.put("changes", lchg);
            Timber.i("Sync: sending and receiving small changes");
            long needSize = sch.toString().length();
            Timber.i("Sync: sending and receiving small changes size:%d", needSize);
            if (needSize > restSpace && Consts.loginAnkiChina()) {
                throwExceptionIfNoSpace(needSize, restSpace);
            } else {
                restSpace -= needSize;
                setRestSpace(restSpace);
                Timber.i("Sync: remain size %d after for small changes ", restSpace);
            }
            JSONObject rchg = mServer.applyChanges(sch);
            throwExceptionIfCancelled(con);
            Timber.i("Sync: merging small changes");
            try {
                mergeChanges(lchg, rchg);
            } catch (UnexpectedSchemaChange e) {
                mServer.abort();
                _forceFullSync();
            }
            // step 3: stream large tables from server
            publishProgress(con, R.string.sync_download_chunk);
            while (true) {
                throwExceptionIfCancelled(con);
                Timber.i("Sync: downloading chunked data");
                JSONObject chunk = mServer.chunk();
                mCol.log("server chunk", chunk);
                Timber.i("Sync: applying chunked data");
                applyChunk(chunk);
                if (chunk.getBoolean("done")) {
                    break;
                }
            }
            // step 4: stream to server
            publishProgress(con, R.string.sync_upload_chunk);
            List<JSONObject> sechs = new ArrayList<>();
            long chunkSize = 0;
            while (true) {
                throwExceptionIfCancelled(con);
                Timber.i("Sync: collecting chunked data");
                JSONObject chunk = chunk();
                mCol.log("client chunk", chunk);
                JSONObject sech = new JSONObject();
                sech.put("chunk", chunk);
                chunkSize += sech.toString().length();
                sechs.add(sech);
                if (chunk.getBoolean("done")) {
                    break;
                }
            }
            Timber.i("Sync: sending chunked data:%d", chunkSize);
            if (chunkSize > restSpace && Consts.loginAnkiChina()) {
                throwExceptionIfNoSpace(chunkSize, restSpace);
            } else {
                restSpace -= chunkSize;
                setRestSpace(restSpace);
            }
            for (JSONObject object : sechs) {
                mServer.applyChunk(object);
            }
            // step 5: sanity check
            JSONObject c = sanityCheck();
            JSONObject sanity = mServer.sanityCheck2(c);
            if (sanity == null || !"ok".equals(sanity.optString("status", "bad"))) {
                mCol.log("sanity check failed", c, sanity);
                return _forceFullSync();
            }
            // finalize
            publishProgress(con, R.string.sync_finish_message);
            Timber.i("Sync: sending finish command");
            long mod = mServer.finish();
            if (mod == 0) {
                return new Object[] { "finishError" };
            }
            Timber.i("Sync: finishing");
            finish(mod);
            publishProgress(con, R.string.sync_writing_db);
            mCol.getDb().getDatabase().setTransactionSuccessful();
        } catch (NoEnoughServerSpaceException e) {
            // mCol.getDb().getDatabase().endTransaction();
            throw new NoEnoughServerSpaceException(e.rest, e.need);
        // e.printStackTrace();
        // mCol.getDb().getDatabase().endTransaction();
        // return new Object[] {"noServerSpace", e.rest, e.need};
        } finally {
            mCol.getDb().getDatabase().endTransaction();
        }
    } catch (NoEnoughServerSpaceException e) {
        Timber.e("NoEnoughServerSpaceException ");
        throw new NoEnoughServerSpaceException(e.rest, e.need);
    // e.printStackTrace();
    // mCol.getDb().getDatabase().endTransaction();
    // return new Object[] {"noServerSpace", e.rest, e.need};
    } catch (IllegalStateException e) {
        throw new RuntimeException(e);
    } catch (OutOfMemoryError e) {
        AnkiDroidApp.sendExceptionReport(e, "Syncer-sync");
        return new Object[] { "OutOfMemoryError" };
    } catch (IOException e) {
        AnkiDroidApp.sendExceptionReport(e, "Syncer-sync");
        return new Object[] { "IOException" };
    }
    return new Object[] { "success", restSpace };
}
Also used : ArrayList(java.util.ArrayList) IOException(java.io.IOException) Response(okhttp3.Response) JSONObject(com.ichi2.utils.JSONObject) JSONObject(com.ichi2.utils.JSONObject) NoEnoughServerSpaceException(com.ichi2.anki.exception.NoEnoughServerSpaceException)

Aggregations

IOException (java.io.IOException)23 JSONObject (com.ichi2.utils.JSONObject)22 Response (okhttp3.Response)20 UnknownHttpResponseException (com.ichi2.anki.exception.UnknownHttpResponseException)8 NoEnoughServerSpaceException (com.ichi2.anki.exception.NoEnoughServerSpaceException)7 File (java.io.File)7 JSONException (com.ichi2.utils.JSONException)6 FileInputStream (java.io.FileInputStream)6 MediaSyncException (com.ichi2.anki.exception.MediaSyncException)5 HostNum (com.ichi2.libanki.sync.HostNum)5 RemoteServer (com.ichi2.libanki.sync.RemoteServer)5 HttpSyncer (com.ichi2.libanki.sync.HttpSyncer)4 ZipFile (java.util.zip.ZipFile)4 JSONArray (com.ichi2.utils.JSONArray)3 FileNotFoundException (java.io.FileNotFoundException)3 InputStream (java.io.InputStream)3 SharedPreferences (android.content.SharedPreferences)2 SQLiteDatabaseCorruptException (android.database.sqlite.SQLiteDatabaseCorruptException)2 Uri (android.net.Uri)2 Pair (android.util.Pair)2