/****************************************************************************************
* Copyright (c) 2009 Daniel Svärd <daniel.svard@gmail.com> *
* Copyright (c) 2009 Edu Zamora <edu.zasu@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 *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.service;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ichi2.anki.BackupManager;
import com.ichi2.anki.Utils;
import com.ichi2.anki.db.AnkiDatabaseManager;
import com.ichi2.anki.model.Card;
import com.ichi2.anki.model.CardModel;
import com.ichi2.anki.model.Deck;
import com.ichi2.anki.model.Fact;
import com.ichi2.anki.model.DeckPicker.AnkiFilter;
import com.ichi2.anki.model.Fact.Field;
import com.ichi2.widget.AnkiDroidWidgetBig;
import com.tomgibara.android.veecheck.util.PrefSettings;
import android.content.Context;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDiskIOException;
import android.os.AsyncTask;
/**
* Loading in the background, so that AnkiDroid does not look like frozen.
*/
public class DeckTask extends AsyncTask<DeckTask.TaskData, DeckTask.TaskData, DeckTask.TaskData> {
private static Logger log = LoggerFactory.getLogger(DeckTask.class);
public static final int TASK_TYPE_LOAD_DECK = 0;
public static final int TASK_TYPE_LOAD_DECK_AND_UPDATE_CARDS = 1;
public static final int TASK_TYPE_SAVE_DECK = 2;
public static final int TASK_TYPE_ANSWER_CARD = 3;
public static final int TASK_TYPE_SUSPEND_CARD = 4;
public static final int TASK_TYPE_MARK_CARD = 5;
public static final int TASK_TYPE_ADD_FACT = 6;
public static final int TASK_TYPE_UPDATE_FACT = 7;
public static final int TASK_TYPE_UNDO = 8;
public static final int TASK_TYPE_REDO = 9;
public static final int TASK_TYPE_LOAD_CARDS = 10;
public static final int TASK_TYPE_BURY_CARD = 11;
public static final int TASK_TYPE_DELETE_CARD = 12;
public static final int TASK_TYPE_LOAD_STATISTICS = 13;
public static final int TASK_TYPE_OPTIMIZE_DECK = 14;
public static final int TASK_TYPE_SET_ALL_DECKS_JOURNAL_MODE = 15;
public static final int TASK_TYPE_DELETE_BACKUPS = 16;
public static final int TASK_TYPE_RESTORE_DECK = 17;
public static final int TASK_TYPE_SORT_CARDS = 18;
public static final int TASK_TYPE_LOAD_TUTORIAL = 19;
public static final int TASK_TYPE_REPAIR_DECK = 20;
public static final int TASK_TYPE_CLOSE_DECK = 21;
/**
* Possible outputs trying to load a deck.
*/
public static final int DECK_LOADED = 0;
public static final int DECK_NOT_LOADED = 1;
public static final int DECK_EMPTY = 2;
public static final int TUTORIAL_NOT_CREATED = 3;
private static DeckTask sInstance;
private static DeckTask sOldInstance;
private int mType;
private TaskListener mListener;
public static DeckTask launchDeckTask(int type, TaskListener listener, TaskData... params) {
sOldInstance = sInstance;
sInstance = new DeckTask();
sInstance.mListener = listener;
sInstance.mType = type;
sInstance.execute(params);
return sInstance;
}
/**
* Block the current thread until the currently running DeckTask instance (if any) has finished.
*/
public static void waitToFinish() {
try {
if ((sInstance != null) && (sInstance.getStatus() != AsyncTask.Status.FINISHED)) {
log.info("DeckTask: wait to finish");
sInstance.get();
}
} catch (Exception e) {
return;
}
}
public static void cancelTask() {
try {
if ((sInstance != null) && (sInstance.getStatus() != AsyncTask.Status.FINISHED)) {
sInstance.cancel(true);
}
} catch (Exception e) {
return;
}
}
public static boolean taskIsRunning() {
try {
if ((sInstance != null) && (sInstance.getStatus() != AsyncTask.Status.FINISHED)) {
return true;
}
} catch (Exception e) {
return true;
}
return false;
}
@Override
protected TaskData doInBackground(TaskData... params) {
// Wait for previous thread (if any) to finish before continuing
try {
if ((sOldInstance != null) && (sOldInstance.getStatus() != AsyncTask.Status.FINISHED)) {
log.info("Waiting for " + sOldInstance.mType + " to finish");
sOldInstance.get();
}
} catch (Exception e) {
log.error("doInBackground - Got exception while waiting for thread to finish: " + e.getMessage());
}
switch (mType) {
case TASK_TYPE_LOAD_DECK:
return doInBackgroundLoadDeck(params);
case TASK_TYPE_LOAD_DECK_AND_UPDATE_CARDS:
TaskData taskData = doInBackgroundLoadDeck(params);
if (taskData.mInteger == DECK_LOADED) {
taskData.mDeck.updateAllCards();
taskData.mCard = taskData.mDeck.getCurrentCard();
}
return taskData;
case TASK_TYPE_SAVE_DECK:
return doInBackgroundSaveDeck(params);
case TASK_TYPE_ANSWER_CARD:
return doInBackgroundAnswerCard(params);
case TASK_TYPE_SUSPEND_CARD:
return doInBackgroundSuspendCard(params);
case TASK_TYPE_MARK_CARD:
return doInBackgroundMarkCard(params);
case TASK_TYPE_ADD_FACT:
return doInBackgroundAddFact(params);
case TASK_TYPE_UPDATE_FACT:
return doInBackgroundUpdateFact(params);
case TASK_TYPE_UNDO:
return doInBackgroundUndo(params);
case TASK_TYPE_REDO:
return doInBackgroundRedo(params);
case TASK_TYPE_LOAD_CARDS:
return doInBackgroundLoadCards(params);
case TASK_TYPE_BURY_CARD:
return doInBackgroundBuryCard(params);
case TASK_TYPE_DELETE_CARD:
return doInBackgroundDeleteCard(params);
case TASK_TYPE_LOAD_STATISTICS:
return doInBackgroundLoadStatistics(params);
case TASK_TYPE_OPTIMIZE_DECK:
return doInBackgroundOptimizeDeck(params);
case TASK_TYPE_SET_ALL_DECKS_JOURNAL_MODE:
return doInBackgroundSetJournalMode(params);
case TASK_TYPE_DELETE_BACKUPS:
return doInBackgroundDeleteBackups();
case TASK_TYPE_RESTORE_DECK:
return doInBackgroundRestoreDeck(params);
case TASK_TYPE_SORT_CARDS:
return doInBackgroundSortCards(params);
case TASK_TYPE_LOAD_TUTORIAL:
return doInBackgroundLoadTutorial(params);
case TASK_TYPE_REPAIR_DECK:
return doInBackgroundRepairDeck(params);
case TASK_TYPE_CLOSE_DECK:
return doInBackgroundCloseDeck(params);
default:
return null;
}
}
@Override
protected void onPreExecute() {
mListener.onPreExecute();
}
@Override
protected void onProgressUpdate(TaskData... values) {
mListener.onProgressUpdate(values);
}
@Override
protected void onPostExecute(TaskData result) {
mListener.onPostExecute(result);
}
private TaskData doInBackgroundAddFact(TaskData[] params) {
// Save the fact
Deck deck = params[0].getDeck();
Fact editFact = params[0].getFact();
LinkedHashMap<Long, CardModel> cardModels = params[0].getCardModels();
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
publishProgress(new TaskData(deck.addFact(editFact, cardModels, false)));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
return null;
}
private TaskData doInBackgroundUpdateFact(TaskData[] params) {
// Save the fact
Deck deck = params[0].getDeck();
Card editCard = params[0].getCard();
Fact editFact = editCard.getFact();
int showQuestion = params[0].getInt();
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
// Start undo routine
String undoName = Deck.UNDO_TYPE_EDIT_CARD;
deck.setUndoStart(undoName, editCard.getId());
// Set modified also updates the text of cards and their modified flags
editFact.setModified(true, deck, false);
editFact.toDb();
deck.updateFactTags(new long[] { editFact.getId() });
deck.flushMod();
// Find all cards based on this fact and update them with the updateCard method.
// for (Card modifyCard : editFact.getUpdatedRelatedCards()) {
// modifyCard.updateQAfields();
// }
// deck.reset();
deck.setUndoEnd(undoName);
if (showQuestion == Reviewer.UPDATE_CARD_NEW_CARD) {
publishProgress(new TaskData(showQuestion, null, deck.getCard()));
} else {
publishProgress(new TaskData(showQuestion, null, deck.cardFromId(editCard.getId())));
}
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundUpdateFact - RuntimeException on updating fact: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundUpdateFact");
return new TaskData(false);
}
return new TaskData(true);
}
private TaskData doInBackgroundAnswerCard(TaskData... params) {
Deck deck = params[0].getDeck();
Card oldCard = params[0].getCard();
int ease = params[0].getInt();
Card newCard = null;
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
if (oldCard != null) {
deck.answerCard(oldCard, ease);
log.info("leech flag: " + oldCard.getLeechFlag());
} else if (DeckManager.deckIsOpenedInBigWidget(deck.getDeckPath())) {
// first card in reviewer is retrieved
log.info("doInBackgroundAnswerCard: get card from big widget");
newCard = AnkiDroidWidgetBig.getCard();
}
if (newCard == null) {
newCard = deck.getCard();
}
if (oldCard != null) {
publishProgress(new TaskData(newCard, oldCard.getLeechFlag(), oldCard.getSuspendedFlag()));
} else {
publishProgress(new TaskData(newCard));
}
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundAnswerCard - RuntimeException on answering card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundAnswerCard");
return new TaskData(false);
}
return new TaskData(true);
}
private TaskData doInBackgroundLoadDeck(TaskData... params) {
String deckFilename = params[0].getString();
int requestingActivity = params[0].getInt();
log.info("doInBackgroundLoadDeck - deckFilename = " + deckFilename + ", requesting activity = " + requestingActivity);
Resources res = AnkiDroidApp.getInstance().getBaseContext().getResources();
publishProgress(new TaskData(AnkiDroidApp.getInstance().getBaseContext().getResources().getString(R.string.finish_operation)));
DeckManager.waitForDeckClosingThread(deckFilename);
int backupResult = BackupManager.RETURN_NULL;
if (PrefSettings.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()).getBoolean("useBackup", true)) {
publishProgress(new TaskData(res.getString(R.string.backup_deck)));
backupResult = BackupManager.backupDeck(deckFilename);
}
if (BackupManager.getFreeDiscSpace(deckFilename) < (StudyOptions.MIN_FREE_SPACE * 1024 * 1024)) {
backupResult = BackupManager.RETURN_LOW_SYSTEM_SPACE;
}
log.info("loadDeck - SD card mounted and existent file -> Loading deck...");
// load deck and set it as main deck
publishProgress(new TaskData(res.getString(R.string.loading_deck)));
Deck deck = DeckManager.getDeck(deckFilename, requestingActivity == DeckManager.REQUESTING_ACTIVITY_STUDYOPTIONS, requestingActivity);
if (deck == null) {
log.info("The database " + deckFilename + " could not be opened");
BackupManager.cleanUpAfterBackupCreation(false);
return new TaskData(DECK_NOT_LOADED, deckFilename);
}
BackupManager.cleanUpAfterBackupCreation(true);
if (deck.hasFinishScheduler()) {
deck.finishScheduler();
}
publishProgress(new TaskData(backupResult));
return new TaskData(DECK_LOADED, deck, null);
}
private TaskData doInBackgroundSaveDeck(TaskData... params) {
Deck deck = params[0].getDeck();
log.info("doInBackgroundSaveAndResetDeck");
if (deck != null) {
try {
deck.commitToDB();
deck.updateCutoff();
if (deck.hasFinishScheduler()) {
deck.finishScheduler();
}
deck.reset();
} catch (SQLiteDiskIOException e) {
log.error("Error on saving deck in background: " + e);
}
}
return null;
}
private TaskData doInBackgroundSuspendCard(TaskData... params) {
Deck deck = params[0].getDeck();
Card oldCard = params[0].getCard();
Card newCard = null;
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
if (oldCard != null) {
String undoName = Deck.UNDO_TYPE_SUSPEND_CARD;
deck.setUndoStart(undoName, oldCard.getId());
if (oldCard.getSuspendedState()) {
oldCard.unsuspend();
newCard = oldCard;
} else {
oldCard.suspend();
newCard = deck.getCard();
}
deck.setUndoEnd(undoName);
}
publishProgress(new TaskData(newCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundSuspendCard - RuntimeException on suspending card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSuspendCard");
return new TaskData(false);
}
return new TaskData(true);
}
private TaskData doInBackgroundMarkCard(TaskData... params) {
Deck deck = params[0].getDeck();
Card currentCard = params[0].getCard();
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
if (currentCard != null) {
String undoName = Deck.UNDO_TYPE_MARK_CARD;
deck.setUndoStart(undoName, currentCard.getId());
if (currentCard.isMarked()) {
deck.deleteTag(currentCard.getFactId(), Deck.TAG_MARKED);
} else {
deck.addTag(currentCard.getFactId(), Deck.TAG_MARKED);
}
deck.resetMarkedTagId();
deck.setUndoEnd(undoName);
}
publishProgress(new TaskData(currentCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundMarkCard - RuntimeException on marking card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundMarkCard");
return new TaskData(false);
}
return new TaskData(true);
}
private TaskData doInBackgroundUndo(TaskData... params) {
Deck deck = params[0].getDeck();
Card newCard;
long currentCardId = params[0].getLong();
boolean inReview = params[0].getBoolean();
long oldCardId = 0;
String undoType = null;
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
oldCardId = deck.undo(currentCardId, inReview);
undoType = deck.getUndoType();
if (undoType == Deck.UNDO_TYPE_SUSPEND_CARD) {
oldCardId = currentCardId;
}
newCard = deck.getCard();
if (oldCardId != 0 && newCard != null && oldCardId != newCard.getId()) {
newCard = deck.cardFromId(oldCardId);
}
publishProgress(new TaskData(newCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundUndo - RuntimeException on undoing: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundUndo");
return new TaskData(undoType, oldCardId, false);
}
return new TaskData(undoType, oldCardId, true);
}
private TaskData doInBackgroundRedo(TaskData... params) {
Deck deck = params[0].getDeck();
Card newCard;
long currentCardId = params[0].getLong();
boolean inReview = params[0].getBoolean();
long oldCardId = 0;
String undoType = null;
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
oldCardId = deck.redo(currentCardId, inReview);
newCard = deck.getCard();
if (oldCardId != 0 && newCard != null && oldCardId != newCard.getId()) {
newCard = deck.cardFromId(oldCardId);
}
publishProgress(new TaskData(newCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
undoType = deck.getUndoType();
if (undoType == Deck.UNDO_TYPE_SUSPEND_CARD) {
undoType = "redo suspend";
}
} catch (RuntimeException e) {
log.error("doInBackgroundRedo - RuntimeException on redoing: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundRedo");
return new TaskData(undoType, oldCardId, false);
}
return new TaskData(undoType, oldCardId, true);
}
private TaskData doInBackgroundLoadCards(TaskData... params) {
Deck deck = params[0].getDeck();
int chunk = params[0].getInt();
log.info("doInBackgroundLoadCards");
String startId = "";
while (!this.isCancelled()) {
ArrayList<HashMap<String, String>> cards = deck.getCards(chunk, startId);
if (cards.size() == 0) {
break;
} else {
publishProgress(new TaskData(cards));
startId = cards.get(cards.size() - 1).get("id");
}
}
return null;
}
private TaskData doInBackgroundDeleteCard(TaskData... params) {
Deck deck = params[0].getDeck();
Card card = params[0].getCard();
Card newCard = null;
Long id = 0l;
log.info("doInBackgroundDeleteCard");
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
id = card.getId();
card.delete();
deck.reset();
newCard = deck.getCard();
publishProgress(new TaskData(newCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundDeleteCard - RuntimeException on deleting card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundDeleteCard");
return new TaskData(String.valueOf(id), 0, false);
}
return new TaskData(String.valueOf(id), 0, true);
}
private TaskData doInBackgroundBuryCard(TaskData... params) {
Deck deck = params[0].getDeck();
Card card = params[0].getCard();
Card newCard = null;
Long id = 0l;
log.info("doInBackgroundBuryCard");
try {
AnkiDb ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
id = card.getId();
deck.buryFact(card.getFactId(), id);
deck.reset();
newCard = deck.getCard();
publishProgress(new TaskData(newCard));
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
} catch (RuntimeException e) {
log.error("doInBackgroundSuspendCard - RuntimeException on suspending card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundBuryCard");
return new TaskData(String.valueOf(id), 0, false);
}
return new TaskData(String.valueOf(id), 0, true);
}
private TaskData doInBackgroundLoadStatistics(TaskData... params) {
log.info("doInBackgroundLoadStatistics");
int type = params[0].getType();
int period = params[0].getInt();
Context context = params[0].getContext();
String[] deckList = params[0].getDeckList();;
boolean result = false;
Resources res = context.getResources();
if (deckList.length == 1) {
if (deckList[0].length() == 0) {
result = Statistics.refreshDeckStatistics(context, DeckManager.getMainDeck(DeckManager.REQUESTING_ACTIVITY_STUDYOPTIONS), type, Integer.parseInt(res.getStringArray(R.array.statistics_period_values)[period]), res.getStringArray(R.array.statistics_type_labels)[type]);
}
} else {
result = Statistics.refreshAllDeckStatistics(context, deckList, type, Integer.parseInt(res.getStringArray(R.array.statistics_period_values)[period]), res.getStringArray(R.array.statistics_type_labels)[type] + " " + res.getString(R.string.statistics_all_decks));
}
publishProgress(new TaskData(result));
return new TaskData(result);
}
private TaskData doInBackgroundOptimizeDeck(TaskData... params) {
log.info("doInBackgroundOptimizeDeck");
Deck deck = params[0].getDeck();
long result = 0;
result = deck.optimizeDeck();
return new TaskData(deck, result);
}
private TaskData doInBackgroundRepairDeck(TaskData... params) {
log.info("doInBackgroundRepairDeck");
String deckPath = params[0].getString();
DeckManager.closeDeck(deckPath, false);
return new TaskData(BackupManager.repairDeck(deckPath));
}
private TaskData doInBackgroundCloseDeck(TaskData... params) {
log.info("doInBackgroundCloseDeck");
String deckPath = params[0].getString();
DeckManager.closeDeck(deckPath, false);
return null;
}
private TaskData doInBackgroundSetJournalMode(TaskData... params) {
log.info("doInBackgroundSetJournalMode");
String path = params[0].getString();
int len = 0;
File[] fileList;
File dir = new File(path);
fileList = dir.listFiles(new AnkiFilter());
if (dir.exists() && dir.isDirectory() && fileList != null) {
len = fileList.length;
} else {
return null;
}
if (len > 0 && fileList != null) {
log.info("Set journal mode: number of anki files = " + len);
for (File file : fileList) {
// on deck open, journal mode will be automatically set, set requesting activity to syncclient to force delete journal mode
String filePath = file.getAbsolutePath();
DeckManager.getDeck(filePath, DeckManager.REQUESTING_ACTIVITY_SYNCCLIENT);
DeckManager.closeDeck(filePath, false);
}
}
return null;
}
private TaskData doInBackgroundDeleteBackups() {
log.info("doInBackgroundDeleteBackups");
return new TaskData(BackupManager.deleteAllBackups());
}
private TaskData doInBackgroundRestoreDeck(TaskData... params) {
log.info("doInBackgroundRestoreDeck");
String[] paths = params[0].getDeckList();
return new TaskData(BackupManager.restoreDeckBackup(paths[0], paths[1]));
}
private TaskData doInBackgroundSortCards(TaskData... params) {
log.info("doInBackgroundSortCards");
Comparator<? super HashMap<String, String>> comparator = params[0].getComparator();
Collections.sort(params[0].getCards(), comparator);
return null;
}
private TaskData doInBackgroundLoadTutorial(TaskData... params) {
log.info("doInBackgroundLoadTutorial");
Resources res = AnkiDroidApp.getInstance().getBaseContext().getResources();
File sampleDeckFile = new File(params[0].getString());
publishProgress(new TaskData(res.getString(R.string.tutorial_load)));
AnkiDb ankiDB = null;
try{
// close open deck
DeckManager.closeMainDeck(false);
// delete any existing tutorial file
if (!sampleDeckFile.exists()) {
sampleDeckFile.delete();
}
// copy the empty deck from the assets to the SD card.
InputStream stream = res.getAssets().open(DeckCreator.EMPTY_DECK_NAME);
Utils.writeToFile(stream, sampleDeckFile.getAbsolutePath());
stream.close();
Deck.initializeEmptyDeck(sampleDeckFile.getAbsolutePath());
String[] questions = res.getStringArray(R.array.tutorial_questions);
String[] answers = res.getStringArray(R.array.tutorial_answers);
String[] sampleQuestions = res.getStringArray(R.array.tutorial_capitals_questions);
String[] sampleAnswers = res.getStringArray(R.array.tutorial_capitals_answers);
Deck deck = DeckManager.getDeck(sampleDeckFile.getAbsolutePath(), DeckManager.REQUESTING_ACTIVITY_STUDYOPTIONS, true);
ankiDB = deck.getDB();
ankiDB.getDatabase().beginTransaction();
try {
CardModel cardModel = null;
int len = Math.min(questions.length, answers.length);
for (int i = 0; i < len + Math.min(sampleQuestions.length, sampleAnswers.length); i++) {
Fact fact = deck.newFact();
if (cardModel == null) {
cardModel = deck.activeCardModels(fact).entrySet().iterator().next().getValue();
}
int fidx = 0;
for (Fact.Field f : fact.getFields()) {
if (fidx == 0) {
f.setValue((i < len) ? questions[i] : sampleQuestions[i - len]);
} else if (fidx == 1) {
f.setValue((i < len) ? answers[i] : sampleAnswers[i - len]);
}
fidx++;
}
if (!deck.importFact(fact, cardModel)) {
sampleDeckFile.delete();
return new TaskData(TUTORIAL_NOT_CREATED);
}
}
deck.setSessionTimeLimit(0);
deck.flushMod();
deck.reset();
ankiDB.getDatabase().setTransactionSuccessful();
} finally {
ankiDB.getDatabase().endTransaction();
}
return new TaskData(DECK_LOADED, deck, null);
} catch (IOException e) {
log.error(e);
log.error("Empty deck could not be copied to the sd card.");
DeckManager.closeMainDeck(false);
sampleDeckFile.delete();
return new TaskData(TUTORIAL_NOT_CREATED);
} catch (RuntimeException e) {
log.error("Error on creating tutorial deck: " + e);
DeckManager.closeMainDeck(false);
sampleDeckFile.delete();
return new TaskData(TUTORIAL_NOT_CREATED);
}
}
public static interface TaskListener {
public void onPreExecute();
public void onPostExecute(TaskData result);
public void onProgressUpdate(TaskData... values);
}
public static class TaskData {
private Deck mDeck;
private Card mCard;
private Fact mFact;
private int mInteger;
private String mMsg;
private boolean previousCardLeech; // answer card resulted in card marked as leech
private boolean previousCardSuspended; // answer card resulted in card marked as leech and suspended
private boolean mBool = false;
private ArrayList<HashMap<String, String>> mCards;
private long mLong;
private Context mContext;
private int mType;
private String[] mDeckList;
private LinkedHashMap<Long, CardModel> mCardModels;
private Comparator<? super HashMap<String, String>> mComparator;
private int[] mIntList;
public TaskData(int value, Deck deck, Card card) {
this(value);
mDeck = deck;
mCard = card;
}
public TaskData(int value, Deck deck, long cardId, boolean bool) {
this(value);
mDeck = deck;
mLong = cardId;
mBool = bool;
}
public TaskData(Card card) {
mCard = card;
previousCardLeech = false;
previousCardSuspended = false;
}
public TaskData(Context context, String[] deckList, int type, int period) {
mContext = context;
mDeckList = deckList;
mType = type;
mInteger = period;
}
public TaskData(Deck deck, Fact fact, LinkedHashMap<Long, CardModel> cardModels) {
mDeck = deck;
mFact = fact;
mCardModels = cardModels;
}
public TaskData(ArrayList<HashMap<String, String>> cards) {
mCards = cards;
}
public TaskData(ArrayList<HashMap<String, String>> cards, Comparator<? super HashMap<String, String>> comparator) {
mCards = cards;
mComparator = comparator;
}
public TaskData(Card card, boolean markedLeech, boolean suspendedLeech) {
mCard = card;
previousCardLeech = markedLeech;
previousCardSuspended = suspendedLeech;
}
public TaskData(Deck deck, String order) {
mDeck = deck;
mMsg = order;
}
public TaskData(Deck deck, int chunk) {
mDeck = deck;
mInteger = chunk;
}
public TaskData(Deck deck, long value) {
mDeck = deck;
mLong = value;
}
public TaskData(boolean bool) {
mBool = bool;
}
public TaskData(int value) {
mInteger = value;
}
public TaskData(String msg) {
mMsg = msg;
}
public TaskData(int value, String msg) {
mMsg = msg;
mInteger = value;
}
public TaskData(String msg, long cardId, boolean bool) {
mMsg = msg;
mLong = cardId;
mBool = bool;
}
public TaskData(int[] intlist) {
mIntList = intlist;
}
public Deck getDeck() {
return mDeck;
}
public ArrayList<HashMap<String, String>> getCards() {
return mCards;
}
public Comparator<? super HashMap<String, String>> getComparator() {
return mComparator;
}
public Card getCard() {
return mCard;
}
public Fact getFact() {
return mFact;
}
public long getLong() {
return mLong;
}
public int getInt() {
return mInteger;
}
public String getString() {
return mMsg;
}
public boolean isPreviousCardLeech() {
return previousCardLeech;
}
public boolean isPreviousCardSuspended() {
return previousCardSuspended;
}
public boolean getBoolean() {
return mBool;
}
public Context getContext() {
return mContext;
}
public int getType() {
return mType;
}
public LinkedHashMap<Long, CardModel> getCardModels() {
return mCardModels;
}
public String[] getDeckList() {
return mDeckList;
}
public int[] getIntList() {
return mIntList;
}
}
}