@Override public void onCorruption(SQLiteDatabase db) { Timber.e("The database has been corrupted..."); AnkiDroidApp.sendExceptionReport( new RuntimeException("Database corrupted"), "AnkiDb.MyDbErrorHandler.onCorruption", "Db has been corrupted "); CollectionHelper.getInstance().closeCollection(false); DatabaseErrorDialog.databaseCorruptFlag = true; }
@Override public boolean commit() { Timber.d("DeckOptions - commit() changes back to database"); try { for (Entry<String, Object> entry : mUpdate.valueSet()) { String key = entry.getKey(); Object value = entry.getValue(); Timber.i("Change value for key '" + key + "': " + value); if (key.equals("maxAnswerTime")) { mOptions.put("maxTaken", (Integer) value); } else if (key.equals("newFactor")) { mOptions.getJSONObject("new").put("initialFactor", (Integer) value * 10); } else if (key.equals("newOrder")) { int newValue = Integer.parseInt((String) value); // Sorting is slow, so only do it if we change order int oldValue = mOptions.getJSONObject("new").getInt("order"); if (oldValue != newValue) { mOptions.getJSONObject("new").put("order", newValue); DeckTask.launchDeckTask( DeckTask.TASK_TYPE_REORDER, mConfChangeHandler, new DeckTask.TaskData(new Object[] {mOptions})); } mOptions.getJSONObject("new").put("order", Integer.parseInt((String) value)); } else if (key.equals("newPerDay")) { mOptions.getJSONObject("new").put("perDay", (Integer) value); } else if (key.equals("newGradIvl")) { JSONArray ja = new JSONArray(); // [graduating, easy] ja.put((Integer) value); ja.put(mOptions.getJSONObject("new").getJSONArray("ints").get(1)); mOptions.getJSONObject("new").put("ints", ja); } else if (key.equals("newEasy")) { JSONArray ja = new JSONArray(); // [graduating, easy] ja.put(mOptions.getJSONObject("new").getJSONArray("ints").get(0)); ja.put((Integer) value); mOptions.getJSONObject("new").put("ints", ja); } else if (key.equals("newBury")) { mOptions.getJSONObject("new").put("bury", (Boolean) value); } else if (key.equals("revPerDay")) { mOptions.getJSONObject("rev").put("perDay", (Integer) value); } else if (key.equals("easyBonus")) { mOptions.getJSONObject("rev").put("ease4", (Integer) value / 100.0f); } else if (key.equals("revIvlFct")) { mOptions.getJSONObject("rev").put("ivlFct", (Integer) value / 100.0f); } else if (key.equals("revMaxIvl")) { mOptions.getJSONObject("rev").put("maxIvl", (Integer) value); } else if (key.equals("revBury")) { mOptions.getJSONObject("rev").put("bury", (Boolean) value); } else if (key.equals("lapMinIvl")) { mOptions.getJSONObject("lapse").put("minInt", (Integer) value); } else if (key.equals("lapLeechThres")) { mOptions.getJSONObject("lapse").put("leechFails", (Integer) value); } else if (key.equals("lapLeechAct")) { mOptions.getJSONObject("lapse").put("leechAction", Integer.parseInt((String) value)); } else if (key.equals("lapNewIvl")) { mOptions.getJSONObject("lapse").put("mult", (Integer) value / 100.0f); } else if (key.equals("showAnswerTimer")) { mOptions.put("timer", (Boolean) value ? 1 : 0); } else if (key.equals("autoPlayAudio")) { mOptions.put("autoplay", (Boolean) value); } else if (key.equals("replayQuestion")) { mOptions.put("replayq", (Boolean) value); } else if (key.equals("desc")) { mDeck.put("desc", (String) value); mCol.getDecks().save(mDeck); } else if (key.equals("newSteps")) { mOptions .getJSONObject("new") .put("delays", StepsPreference.convertToJSON((String) value)); } else if (key.equals("lapSteps")) { mOptions .getJSONObject("lapse") .put("delays", StepsPreference.convertToJSON((String) value)); } else if (key.equals("deckConf")) { long newConfId = Long.parseLong((String) value); mOptions = mCol.getDecks().getConf(newConfId); DeckTask.launchDeckTask( DeckTask.TASK_TYPE_CONF_CHANGE, mConfChangeHandler, new DeckTask.TaskData(new Object[] {mDeck, mOptions})); // Restart to reflect the new preference values restartActivity(); } else if (key.equals("confRename")) { String newName = (String) value; if (!TextUtils.isEmpty(newName)) { mOptions.put("name", newName); } } else if (key.equals("confReset")) { if ((Boolean) value) { DeckTask.launchDeckTask( DeckTask.TASK_TYPE_CONF_RESET, mConfChangeHandler, new DeckTask.TaskData(new Object[] {mOptions})); } } else if (key.equals("confAdd")) { String newName = (String) value; if (!TextUtils.isEmpty(newName)) { // New config clones current config long id = mCol.getDecks().confId(newName, mOptions.toString()); mDeck.put("conf", id); mCol.getDecks().save(mDeck); } } else if (key.equals("confRemove")) { if (mOptions.getLong("id") == 1) { // Don't remove the options group if it's the default group Themes.showThemedToast( DeckOptions.this, getResources().getString(R.string.default_conf_delete_error), false); } else { // Remove options group, handling the case where the user needs to confirm full sync try { remConf(); } catch (ConfirmModSchemaException e) { // Libanki determined that a full sync will be required, so confirm with the user // before proceeding // TODO : Use ConfirmationDialog DialogFragment -- not compatible with // PreferenceActivity new MaterialDialog.Builder(DeckOptions.this) .content(R.string.full_sync_confirmation) .positiveText(R.string.dialog_ok) .negativeText(R.string.dialog_cancel) .callback( new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { mCol.modSchemaNoCheck(); try { remConf(); } catch (ConfirmModSchemaException e) { // This should never be reached as we just forced modSchema throw new RuntimeException(e); } } }) .build() .show(); } } } else if (key.equals("confSetSubdecks")) { if ((Boolean) value) { DeckTask.launchDeckTask( DeckTask.TASK_TYPE_CONF_SET_SUBDECKS, mConfChangeHandler, new DeckTask.TaskData(new Object[] {mDeck, mOptions})); } } } } catch (JSONException e) { throw new RuntimeException(e); } // save conf try { mCol.getDecks().save(mOptions); } catch (RuntimeException e) { Timber.e("DeckOptions - RuntimeException on saving conf: " + e); AnkiDroidApp.sendExceptionReport(e, "DeckOptionsSaveConf"); setResult(DeckPicker.RESULT_DB_ERROR); finish(); } // make sure we refresh the parent cached values cacheValues(); buildLists(); updateSummaries(); // and update any listeners for (OnSharedPreferenceChangeListener listener : listeners) { listener.onSharedPreferenceChanged(DeckPreferenceHack.this, null); } return true; }
/** * Convenience method for querying the database for an entire column. The column will be returned * as an ArrayList of the specified class. See Deck.initUndo() for a usage example. * * @param type The class of the column's data type. Example: int.class, String.class. * @param query The SQL query statement. * @param column The column id in the result set to return. * @return An ArrayList with the contents of the specified column. */ public <T> ArrayList<T> queryColumn(Class<T> type, String query, int column) { int nullExceptionCount = 0; InvocationTargetException nullException = null; // to catch the null exception for reporting ArrayList<T> results = new ArrayList<T>(); Cursor cursor = null; try { cursor = mDatabase.rawQuery(query, null); String methodName = getCursorMethodName(type.getSimpleName()); while (cursor.moveToNext()) { try { // The magical line. Almost as illegible as python code ;) results.add( type.cast(Cursor.class.getMethod(methodName, int.class).invoke(cursor, column))); } catch (InvocationTargetException e) { if (cursor.isNull(column)) { // null value encountered nullExceptionCount++; if (nullExceptionCount == 1) { // Toast and error report first time only nullException = e; Toast.makeText( AnkiDroidApp.getInstance().getBaseContext(), "Error report pending: unexpected null in database.", Toast.LENGTH_LONG) .show(); } continue; // attempt to skip this null record } else { throw new RuntimeException(e); } } } } catch (NoSuchMethodException e) { // This is really coding error, so it should be revealed if it ever happens throw new RuntimeException(e); } catch (IllegalArgumentException e) { // This is really coding error, so it should be revealed if it ever happens throw new RuntimeException(e); } catch (IllegalAccessException e) { // This is really coding error, so it should be revealed if it ever happens throw new RuntimeException(e); } finally { if (cursor != null) { cursor.close(); } if (nullExceptionCount > 0) { if (nullException != null) { StringBuilder sb = new StringBuilder(); sb.append("AnkiDb.queryColumn (column " + column + "): "); sb.append("Exception due to null. Query: " + query); sb.append(" Null occurrences during this query: " + nullExceptionCount); AnkiDroidApp.sendExceptionReport( nullException, "queryColumn_encounteredNull", sb.toString()); Timber.w(sb.toString()); } else { // nullException not properly initialized StringBuilder sb = new StringBuilder(); sb.append("AnkiDb.queryColumn(): Critical error -- "); sb.append("unable to pass in the actual exception to error reporting."); AnkiDroidApp.sendExceptionReport( new RuntimeException("queryColumn null"), "queryColumn_encounteredNull", sb.toString()); Timber.e(sb.toString()); } } } return results; }