private TaskData doInBackgroundSuspendCard(TaskData... params) { long start, stop; Deck deck = params[0].getDeck(); Card oldCard = params[0].getCard(); Card newCard; AnkiDb.database.beginTransaction(); try { if (oldCard != null) { start = System.currentTimeMillis(); deck.suspendCard(oldCard.id); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackgroundSuspendCard - Suspended card in " + (stop - start) + " ms."); } start = System.currentTimeMillis(); newCard = deck.getCard(); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackgroundSuspendCard - Loaded new card in " + (stop - start) + " ms."); publishProgress(new TaskData(newCard)); AnkiDb.database.setTransactionSuccessful(); } finally { AnkiDb.database.endTransaction(); } return null; }
@Override protected Payload doInBackground(Payload... args) { Payload data = doInBackgroundLoadDeck(args); if (data.returnType == DeckTask.DECK_LOADED) { double now = System.currentTimeMillis(); HashMap<String, Object> results = (HashMap<String, Object>) data.result; Deck deck = (Deck) results.get("deck"); // deck.beforeUpdateCards(); // deck.updateAllCards(); SharedDeckDownload download = (SharedDeckDownload) args[0].data[0]; SharedPreferences pref = PrefSettings.getSharedPrefs(getBaseContext()); String updatedCardsPref = "numUpdatedCards:" + mDestination + "/tmp/" + download.getTitle() + ".anki.updating"; long totalCards = deck.retrieveCardCount(); download.setNumTotalCards((int) totalCards); long updatedCards = pref.getLong(updatedCardsPref, 0); download.setNumUpdatedCards((int) updatedCards); long batchSize = Math.max(100, totalCards / 200); mRecentBatchTimings = new long[sRunningAvgLength]; mTotalBatches = ((double) totalCards) / batchSize; int currentBatch = (int) (updatedCards / batchSize); long runningAvgCount = 0; long batchStart; mElapsedTime = 0; while (updatedCards < totalCards && download.getStatus() == SharedDeckDownload.STATUS_UPDATING) { batchStart = System.currentTimeMillis(); updatedCards = deck.updateAllCardsFromPosition(updatedCards, batchSize); Editor editor = pref.edit(); editor.putLong(updatedCardsPref, updatedCards); editor.commit(); download.setNumUpdatedCards((int) updatedCards); publishProgress(); estimateTimeToCompletion( download, currentBatch, runningAvgCount, System.currentTimeMillis() - batchStart); currentBatch++; runningAvgCount++; } if (download.getStatus() == SharedDeckDownload.STATUS_UPDATING) { data.success = true; } else if (download.getStatus() == SharedDeckDownload.STATUS_PAUSED) { Editor editor = pref.edit(); String pausedPref = "paused:" + mDestination + "/tmp/" + download.getTitle() + ".anki.updating"; editor.putBoolean(pausedPref, true); editor.commit(); data.success = false; Log.i(AnkiDroidApp.TAG, "pausing deck " + download.getTitle()); } else if (download.getStatus() == SharedDeckDownload.STATUS_CANCELLED) { data.success = false; } // Log.i(AnkiDroidApp.TAG, "Time to update deck = " + download.getEstTimeToCompletion() + " // sec."); // deck.afterUpdateCards(); } else { data.success = false; } return data; }
private TaskData doInBackgroundAnswerCard(TaskData... params) { long start, stop; Deck deck = params[0].getDeck(); Card oldCard = params[0].getCard(); int ease = params[0].getInt(); Card newCard; if (oldCard != null) { start = System.currentTimeMillis(); oldCard.temporarilySetLowestPriority(); deck.decreaseCounts(oldCard); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackground - Set old card 0 priority in " + (stop - start) + " ms."); } start = System.currentTimeMillis(); newCard = deck.getCard(); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackground - Loaded new card in " + (stop - start) + " ms."); publishProgress(new TaskData(newCard)); if (ease != 0 && oldCard != null) { start = System.currentTimeMillis(); deck.answerCard(oldCard, ease); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackground - Answered old card in " + (stop - start) + " ms."); } return null; }
@Override protected void onPostExecute(Payload result) { super.onPostExecute(result); HashMap<String, Object> results = (HashMap<String, Object>) result.result; Deck deck = (Deck) results.get("deck"); // Close the previously opened deck. if (deck != null) { deck.closeDeck(); } SharedDeckDownload download = (SharedDeckDownload) result.data[0]; SharedPreferences pref = PrefSettings.getSharedPrefs(getBaseContext()); Editor editor = pref.edit(); Log.i(AnkiDroidApp.TAG, "Finished deck " + download.getTitle() + " " + result.success); if (result.success) { // Put updated cards to 0 // TODO: Why do we need to zero the updated cards? editor.putLong( "numUpdatedCards:" + mDestination + "/tmp/" + download.getTitle() + ".anki.updating", 0); editor.commit(); // Move deck and media to the default deck path new File(mDestination + "/tmp/" + download.getTitle() + ".anki.updating") .renameTo(new File(mDestination + "/" + download.getTitle() + ".anki")); new File(mDestination + "/tmp/" + download.getTitle() + ".media/") .renameTo(new File(mDestination + "/" + download.getTitle() + ".media/")); mSharedDeckDownloads.remove(download); showNotification(download.getTitle()); } else { // If paused do nothing, if cancelled clean up if (download.getStatus() == Download.STATUS_CANCELLED) { try { new File(mDestination + "/tmp/" + download.getTitle() + ".anki.updating").delete(); File mediaFolder = new File(mDestination + "/tmp/" + download.getTitle() + ".media/"); if (mediaFolder != null && mediaFolder.listFiles() != null) { for (File f : mediaFolder.listFiles()) { f.delete(); } mediaFolder.delete(); } } catch (SecurityException e) { Log.e(AnkiDroidApp.TAG, "SecurityException = " + e.getMessage()); e.printStackTrace(); } editor.remove( "numUpdatedCards:" + mDestination + "/tmp/" + download.getTitle() + ".anki.updating"); editor.remove( "paused:" + mDestination + "/tmp/" + download.getTitle() + ".anki.updating"); editor.commit(); mSharedDeckDownloads.remove(download); } } notifySharedDeckObservers(); stopIfFinished(); }
private void updateList() { mCardsAdapter.notifyDataSetChanged(); int count = mCards.size(); setTitle( getResources() .getQuantityString( R.plurals.card_browser_title, count, mDeck.getDeckName(), count, mAllCards.size())); }
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); int selectedPosition = ((AdapterView.AdapterContextMenuInfo) menuInfo).position; Resources res = getResources(); @SuppressWarnings("unused") MenuItem item; mSelectedCard = mDeck.cardFromId(Long.parseLong(mCards.get(selectedPosition).get("id"))); if (mSelectedCard.isMarked()) { item = menu.add( Menu.NONE, CONTEXT_MENU_MARK, Menu.NONE, res.getString(R.string.card_browser_unmark_card)); mIsMarked = true; Log.i(AnkiDroidApp.TAG, "Selected Card is currently marked"); } else { item = menu.add( Menu.NONE, CONTEXT_MENU_MARK, Menu.NONE, res.getString(R.string.card_browser_mark_card)); mIsMarked = false; } if (mSelectedCard.getSuspendedState()) { item = menu.add( Menu.NONE, CONTEXT_MENU_SUSPEND, Menu.NONE, res.getString(R.string.card_browser_unsuspend_card)); mIsSuspended = true; Log.i(AnkiDroidApp.TAG, "Selected Card is currently suspended"); } else { item = menu.add( Menu.NONE, CONTEXT_MENU_SUSPEND, Menu.NONE, res.getString(R.string.card_browser_suspend_card)); mIsSuspended = false; } item = menu.add( Menu.NONE, CONTEXT_MENU_DELETE, Menu.NONE, res.getString(R.string.card_browser_delete_card)); item = menu.add( Menu.NONE, CONTEXT_MENU_DETAILS, Menu.NONE, res.getString(R.string.card_browser_card_details)); menu.setHeaderTitle(mCards.get(selectedPosition).get("question")); }
private TaskData doInBackgroundAnswerCard(TaskData... params) { long start, start2; Deck deck = params[0].getDeck(); Card oldCard = params[0].getCard(); int ease = params[0].getInt(); Card newCard; start2 = System.currentTimeMillis(); AnkiDb.database.beginTransaction(); try { if (oldCard != null) { start = System.currentTimeMillis(); deck.answerCard(oldCard, ease); Log.v( TAG, "doInBackgroundAnswerCard - Answered card in " + (System.currentTimeMillis() - start) + " ms."); } start = System.currentTimeMillis(); newCard = deck.getCard(); Log.v( TAG, "doInBackgroundAnswerCard - Loaded new card in " + (System.currentTimeMillis() - start) + " ms."); publishProgress(new TaskData(newCard)); AnkiDb.database.setTransactionSuccessful(); } finally { AnkiDb.database.endTransaction(); } Log.w( TAG, "doInBackgroundAnswerCard - DB operations in " + (System.currentTimeMillis() - start2) + " ms."); return null; }
private void updateCard(Card card, ArrayList<HashMap<String, String>> list, int position) { list.get(position).put("question", Utils.stripHTML(card.getQuestion().replace("<br>", "\n"))); list.get(position).put("answer", Utils.stripHTML(card.getAnswer().replace("<br>", "\n"))); for (long cardId : mDeck.getCardsFromFactId(card.getFactId())) { if (cardId != card.getId()) { int positionC = getPosition(mCards, cardId); int positionA = getPosition(mAllCards, cardId); Card c = mDeck.cardFromId(cardId); String question = Utils.stripHTML(c.getQuestion().replace("<br>", "\n")); String answer = Utils.stripHTML(c.getAnswer().replace("<br>", "\n")); if (positionC != -1) { mCards.get(positionC).put("question", question); mCards.get(positionC).put("answer", answer); } mAllCards.get(positionA).put("question", question); mAllCards.get(positionA).put("answer", answer); } } }
private TaskData doInBackgroundUpdateFact(TaskData[] params) { // Save the fact Deck deck = params[0].getDeck(); Card editCard = params[0].getCard(); Fact editFact = editCard.fact; editFact.toDb(); LinkedList<Card> saveCards = editFact.getUpdatedRelatedCards(); Iterator<Card> iter = saveCards.iterator(); while (iter.hasNext()) { Card modifyCard = iter.next(); deck.updateCard(modifyCard); } // Find all cards based on this fact and update them with the updateCard method. publishProgress(new TaskData(deck.getCurrentCard())); return null; }
private TaskData doInBackgroundSuspendCard(TaskData... params) { long start, stop; Deck deck = params[0].getDeck(); Card oldCard = params[0].getCard(); Card newCard; if (oldCard != null) { start = System.currentTimeMillis(); deck.suspendCard(oldCard.id); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackgroundSuspendCard - Suspended card in " + (stop - start) + " ms."); } start = System.currentTimeMillis(); newCard = deck.getCard(); stop = System.currentTimeMillis(); Log.v(TAG, "doInBackgroundSuspendCard - Loaded new card in " + (stop - start) + " ms."); publishProgress(new TaskData(newCard)); return null; }
private Payload doInBackgroundLoadDeck(Payload... params) { Payload data = params[0]; SharedDeckDownload download = (SharedDeckDownload) data.data[0]; String deckFilename = mDestination + "/tmp/" + download.getTitle() + ".anki.updating"; Log.i(AnkiDroidApp.TAG, "doInBackgroundLoadDeck - deckFilename = " + deckFilename); Log.i(AnkiDroidApp.TAG, "loadDeck - SD card mounted and existent file -> Loading deck..."); try { // Open the right deck. Deck deck = Deck.openDeck(deckFilename); // Start by getting the first card and displaying it. Card card = deck.getCard(); Log.i(AnkiDroidApp.TAG, "Deck loaded!"); // Set the result data.returnType = DeckTask.DECK_LOADED; HashMap<String, Object> results = new HashMap<String, Object>(); results.put("deck", deck); results.put("card", card); results.put("position", download.getNumUpdatedCards()); data.result = results; return data; } catch (SQLException e) { Log.i( AnkiDroidApp.TAG, "The database " + deckFilename + " could not be opened = " + e.getMessage()); data.success = false; data.returnType = DeckTask.DECK_NOT_LOADED; data.exception = e; return data; } catch (CursorIndexOutOfBoundsException e) { // XXX: Where is this exception thrown? Log.i(AnkiDroidApp.TAG, "The deck has no cards = " + e.getMessage()); data.success = false; data.returnType = DeckTask.DECK_EMPTY; data.exception = e; return data; } }
private TaskData doInBackgroundLoadDeck(TaskData... params) { String deckFilename = params[0].getString(); Log.i(TAG, "doInBackgroundLoadDeck - deckFilename = " + deckFilename); Log.i(TAG, "loadDeck - SD card mounted and existent file -> Loading deck..."); try { // Open the right deck. Deck deck = Deck.openDeck(deckFilename); // Start by getting the first card and displaying it. Card card = deck.getCard(); Log.i(TAG, "Deck loaded!"); return new TaskData(AnkiDroid.DECK_LOADED, deck, card); } catch (SQLException e) { Log.i(TAG, "The database " + deckFilename + " could not be opened = " + e.getMessage()); return new TaskData(AnkiDroid.DECK_NOT_LOADED); } catch (CursorIndexOutOfBoundsException e) { Log.i(TAG, "The deck has no cards = " + e.getMessage()); ; return new TaskData(AnkiDroid.DECK_EMPTY); } }
@Override public void onPostExecute(DeckTask.TaskData result) { mUndoRedoCardId = result.getLong(); String undoType = result.getString(); if (undoType.equals(Deck.UNDO_TYPE_DELETE_CARD)) { int position = getPosition(mDeletedCards, mUndoRedoCardId); if (position != -1) { HashMap<String, String> data = new HashMap<String, String>(); data.put("id", mDeletedCards.get(position).get("id")); data.put("question", mDeletedCards.get(position).get("question")); data.put("answer", mDeletedCards.get(position).get("answer")); data.put("marSus", mDeletedCards.get(position).get("marSus")); mAllCards.add(Integer.parseInt(mDeletedCards.get(position).get("allCardPos")), data); mDeletedCards.remove(position); updateCardsList(); } else { deleteCard(Long.toString(mUndoRedoCardId), getPosition(mCards, mUndoRedoCardId)); } mProgressDialog.dismiss(); } else { mUndoRedoCard = mDeck.cardFromId(mUndoRedoCardId); if (undoType.equals(Deck.UNDO_TYPE_EDIT_CARD)) { mUndoRedoCard.fromDB(mUndoRedoCardId); int pos = getPosition(mAllCards, mUndoRedoCardId); updateCard(mUndoRedoCard, mAllCards, pos); pos = getPosition(mCards, mUndoRedoCardId); if (pos != -1) { updateCard(mUndoRedoCard, mCards, pos); } updateList(); mProgressDialog.dismiss(); } else if (undoType.equals(Deck.UNDO_TYPE_MARK_CARD)) { markCards(mUndoRedoCard.getFactId(), mUndoRedoCard.isMarked()); mProgressDialog.dismiss(); } else if (undoType.equals(Deck.UNDO_TYPE_SUSPEND_CARD)) { suspendCard( mUndoRedoCard, getPosition(mCards, mUndoRedoCardId), mUndoRedoCard.getSuspendedState()); mProgressDialog.dismiss(); } else { mUndoRedoDialogShowing = true; getCards(); } } }
private void markCards(long factId, boolean mark) { for (long cardId : mDeck.getCardsFromFactId(factId)) { int positionC = getPosition(mCards, cardId); int positionA = getPosition(mAllCards, cardId); String marSus = mAllCards.get(positionA).get("marSus"); if (mark) { marSus = "1" + marSus.substring(1, 2); if (positionC != -1) { mCards.get(positionC).put("marSus", marSus); } mAllCards.get(positionA).put("marSus", marSus); } else { marSus = "0" + marSus.substring(1, 2); if (positionC != -1) { mCards.get(positionC).put("marSus", marSus); } mAllCards.get(positionA).put("marSus", marSus); } } updateList(); }
@Override protected void onCreate(Bundle savedInstanceState) { Themes.applyTheme(this); super.onCreate(savedInstanceState); setContentView(R.layout.card_browser); mDeck = AnkiDroidApp.deck(); mDeck.resetUndo(); mBackground = Themes.getCardBrowserBackground(); SharedPreferences preferences = PrefSettings.getSharedPrefs(getBaseContext()); mrelativeBrowserFontSize = preferences.getInt("relativeCardBrowserFontSize", DEFAULT_FONT_SIZE_RATIO); mPrefFixArabic = preferences.getBoolean("fixArabicText", false); mCards = new ArrayList<HashMap<String, String>>(); mAllCards = new ArrayList<HashMap<String, String>>(); mCardsListView = (ListView) findViewById(R.id.card_browser_list); mCardsAdapter = new SizeControlledListAdapter( this, mCards, R.layout.card_item, new String[] {"question", "answer", "marSus"}, new int[] {R.id.card_question, R.id.card_answer, R.id.card_item}, mrelativeBrowserFontSize); mCardsAdapter.setViewBinder( new SimpleAdapter.ViewBinder() { @Override public boolean setViewValue(View view, Object arg1, String text) { if (view.getId() == R.id.card_item) { int which = BACKGROUND_NORMAL; if (text.equals("11")) { which = BACKGROUND_MARKED_SUSPENDED; } else if (text.substring(1, 2).equals("1")) { which = BACKGROUND_SUSPENDED; } else if (text.substring(0, 1).equals("1")) { which = BACKGROUND_MARKED; } view.setBackgroundResource(mBackground[which]); return true; } return false; } }); mCardsListView.setAdapter(mCardsAdapter); mCardsListView.setOnItemClickListener( new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent editCard = new Intent(CardBrowser.this, CardEditor.class); mPositionInCardsList = position; mSelectedCard = mDeck.cardFromId(Long.parseLong(mCards.get(mPositionInCardsList).get("id"))); sEditorCard = mSelectedCard; editCard.putExtra("callfromcardbrowser", true); startActivityForResult(editCard, EDIT_CARD); if (Integer.valueOf(android.os.Build.VERSION.SDK) > 4) { ActivityTransitionAnimation.slide(CardBrowser.this, ActivityTransitionAnimation.LEFT); } } }); registerForContextMenu(mCardsListView); mSearchEditText = (EditText) findViewById(R.id.card_browser_search); mSearchEditText.addTextChangedListener( new TextWatcher() { public void afterTextChanged(Editable s) { mTimerHandler.removeCallbacks(updateList); mTimerHandler.postDelayed(updateList, WAIT_TIME_UNTIL_UPDATE); } public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void onTextChanged(CharSequence s, int start, int before, int count) {} }); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); setTitle(mDeck.getDeckName()); allTags = null; mSelectedTags = new HashSet<String>(); getCards(); }
public static Deck openDeck(String path) throws SQLException { Deck deck = null; Cursor cursor = null; Log.i(TAG, "openDeck - Opening database " + path); AnkiDb.openDatabase(path); try { // Read in deck table columns cursor = AnkiDb.database.rawQuery("SELECT *" + " FROM decks" + " LIMIT 1", null); if (!cursor.moveToFirst()) return null; deck = new Deck(); deck.id = cursor.getLong(0); deck.created = cursor.getDouble(1); deck.modified = cursor.getDouble(2); deck.description = cursor.getString(3); deck.version = cursor.getInt(4); deck.currentModelId = cursor.getLong(5); deck.syncName = cursor.getString(6); deck.lastSync = cursor.getDouble(7); deck.hardIntervalMin = cursor.getDouble(8); deck.hardIntervalMax = cursor.getDouble(9); deck.midIntervalMin = cursor.getDouble(10); deck.midIntervalMax = cursor.getDouble(11); deck.easyIntervalMin = cursor.getDouble(12); deck.easyIntervalMax = cursor.getDouble(13); deck.delay0 = cursor.getDouble(14); deck.delay1 = cursor.getDouble(15); deck.delay2 = cursor.getDouble(16); deck.collapseTime = cursor.getDouble(17); deck.highPriority = cursor.getString(18); deck.medPriority = cursor.getString(19); deck.lowPriority = cursor.getString(20); deck.suspended = cursor.getString(21); deck.newCardOrder = cursor.getInt(22); deck.newCardSpacing = cursor.getInt(23); deck.failedCardMax = cursor.getInt(24); deck.newCardsPerDay = cursor.getInt(25); deck.sessionRepLimit = cursor.getInt(26); deck.sessionTimeLimit = cursor.getInt(27); deck.utcOffset = cursor.getDouble(28); deck.cardCount = cursor.getInt(29); deck.factCount = cursor.getInt(30); deck.failedNowCount = cursor.getInt(31); deck.failedSoonCount = cursor.getInt(32); deck.revCount = cursor.getInt(33); deck.newCount = cursor.getInt(34); deck.revCardOrder = cursor.getInt(35); Log.i(TAG, "openDeck - Read " + cursor.getColumnCount() + " columns from decks table."); } finally { if (cursor != null) cursor.close(); } Log.i( TAG, String.format( ENGLISH_LOCALE, "openDeck - modified: %f currentTime: %f", deck.modified, System.currentTimeMillis() / 1000.0)); deck.initVars(); // Ensure necessary indices are available deck.updateDynamicIndices(); // Save counts to determine if we should save deck after check int oldCount = deck.failedSoonCount + deck.revCount + deck.newCount; // Update counts deck.rebuildQueue(); try { // Unsuspend reviewed early & buried cursor = AnkiDb.database.rawQuery( "SELECT id " + "FROM cards " + "WHERE type in (0,1,2) and " + "isDue = 0 and " + "priority in (-1,-2)", null); if (cursor.moveToFirst()) { int count = cursor.getCount(); long[] ids = new long[count]; for (int i = 0; i < count; i++) { ids[i] = cursor.getLong(0); cursor.moveToNext(); } deck.updatePriorities(ids); deck.checkDue(); } } finally { if (cursor != null) cursor.close(); } // Save deck to database if it has been modified if ((oldCount != (deck.failedSoonCount + deck.revCount + deck.newCount)) || deck.modifiedSinceSave()) deck.commitToDB(); // Create a temporary view for random new cards. Randomizing the cards by themselves // as is done in desktop Anki in Deck.randomizeNewCards() takes too long. AnkiDb.database.execSQL( "CREATE TEMPORARY VIEW acqCardsRandom AS " + "SELECT * FROM cards " + "WHERE type = 2 AND isDue = 1 " + "ORDER BY RANDOM()"); return deck; }
@Override public boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(MENU_UNDO).setEnabled(mDeck.undoAvailable()); menu.findItem(MENU_REDO).setEnabled(mDeck.redoAvailable()); return true; }