use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.
the class StudyOptionsFragment method refreshOption.
private void refreshOption() {
if (mLoadWithDeckOptions) {
mLoadWithDeckOptions = false;
Deck deck = getCol().getDecks().current();
if (deck.getInt("dyn") != 0 && deck.has("empty")) {
deck.remove("empty");
}
mProgressDialog = StyledProgressDialog.show(getActivity(), "", getResources().getString(R.string.rebuild_filtered_deck), true);
CollectionTask.launchCollectionTask(REBUILD_CRAM, getCollectionTaskListener(true));
} else {
CollectionTask.waitToFinish();
refreshInterface(true);
}
}
use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.
the class StudyOptionsFragment method getCollectionTaskListener.
/**
* Returns a listener that rebuilds the interface after execute.
*
* @param refreshDecklist If true, the listener notifies the parent activity to update its deck list
* to reflect the latest values.
*/
private TaskListener getCollectionTaskListener(final boolean refreshDecklist) {
return new TaskListener() {
@Override
public void onPreExecute() {
}
@Override
public void onPostExecute(TaskData result) {
dismissProgressDialog();
if (result != null) {
// Get the return values back from the AsyncTask
Object[] obj = result.getObjArray();
int newCards = (Integer) obj[0];
int lrnCards = (Integer) obj[1];
int revCards = (Integer) obj[1] + (Integer) obj[2];
int totalNew = (Integer) obj[3];
int totalCards = (Integer) obj[4];
Timber.i("start refresh list data:" + newCards + "," + lrnCards + "," + revCards + "," + totalNew + "," + totalCards);
// Don't do anything if the fragment is no longer attached to it's Activity or col has been closed
if (getActivity() == null) {
Timber.e("StudyOptionsFragment.mRefreshFragmentListener :: can't refresh");
return;
}
// #5506 If we have no view, short circuit all UI logic
if (mStudyOptionsView == null) {
tryOpenCramDeckOptions();
return;
}
// Reinitialize controls incase changed to filtered deck
initAllContentViews(mStudyOptionsView);
// Set the deck name
String fullName;
Deck deck = getCol().getDecks().current();
// Main deck name
fullName = deck.getString("name");
String[] name = Decks.path(fullName);
StringBuilder nameBuilder = new StringBuilder();
if (name.length > 0) {
nameBuilder.append(name[name.length - 1]);
}
// if (name.length > 1) {
// nameBuilder.append("\n").append(name[1]);
// }
// if (name.length > 3) {
// nameBuilder.append("...");
// }
// if (name.length > 2) {
// nameBuilder.append("\n").append(name[name.length - 1]);
// }
// mTextDeckName.setText(nameBuilder.toString());
mDeckListAdapter.mTextDeckName = nameBuilder.toString();
if (tryOpenCramDeckOptions()) {
return;
}
// Switch between the empty view, the ordinary view, and the "congratulations" view
boolean isDynamic = deck.optInt("dyn", 0) != 0;
if (totalCards == 0 && !isDynamic) {
mCurrentContentView = CONTENT_EMPTY;
mDeckListAdapter.mDeckInfoLayoutVisible = View.VISIBLE;
mDeckListAdapter.mTextCongratsMessageVisible = View.VISIBLE;
// mDeckListAdapter.mTextCongratsMessage=getString(R.string.studyoptions_empty);
mDeckListAdapter.mButtonStartEnable = false;
mDeckListAdapter.mTextButtonStart = getString(R.string.studyoptions_start);
} else if (newCards + lrnCards + revCards == 0) {
mCurrentContentView = CONTENT_CONGRATS;
if (!isDynamic) {
mDeckListAdapter.mDeckInfoLayoutVisible = View.GONE;
mDeckListAdapter.mButtonStartEnable = true;
mDeckListAdapter.mTextButtonStart = getString(R.string.add_today_study_amount);
} else {
mDeckListAdapter.mButtonStartEnable = true;
mDeckListAdapter.mTextButtonStart = getString(R.string.add_today_study_amount);
}
mDeckListAdapter.mTextCongratsMessageVisible = View.VISIBLE;
// mDeckListAdapter.mTextCongratsMessage=getCol().getSched().finishedMsg(getActivity()).toString();
// mTextCongratsMessage.setText(getCol().getSched().finishedMsg(getActivity()));
} else {
mCurrentContentView = CONTENT_STUDY_OPTIONS;
mDeckListAdapter.mDeckInfoLayoutVisible = View.VISIBLE;
mDeckListAdapter.mTextCongratsMessageVisible = View.GONE;
mDeckListAdapter.mButtonStartEnable = true;
mDeckListAdapter.mTextButtonStart = getString(R.string.studyoptions_start);
}
mDeckListAdapter.setButtonStartClickListener(mButtonClickListener);
mDeckListAdapter.setSelfStudyClickListener(mSelfStudyListener);
// Set deck description
String desc;
if (isDynamic) {
desc = getResources().getString(R.string.dyn_deck_desc);
} else {
desc = "";
// desc = getCol().getDecks().getActualDescription();
}
if (desc.length() > 0) {
mDeckListAdapter.mTextDeckDescription = desc;
mDeckListAdapter.mTextDeckDescriptionVisible = View.VISIBLE;
// mTextDeckDescription.setText(formatDescription(desc));
// mTextDeckDescription.setVisibility(View.VISIBLE);
} else {
mDeckListAdapter.mTextDeckDescriptionVisible = View.GONE;
}
// Set new/learn/review card counts
mDeckListAdapter.mTextTodayNew = String.valueOf(newCards);
mDeckListAdapter.mTextTodayRev = String.valueOf(revCards);
// Set the total number of new cards in deck
if (totalNew < NEW_CARD_COUNT_TRUNCATE_THRESHOLD) {
// if it hasn't been truncated by libanki then just set it usually
// mTextNewTotal.setText(String.valueOf(totalNew));
} else {
// mTextNewTotal.setText(">1000");
if (mFullNewCountThread != null) {
// a thread was previously made -- interrupt it
mFullNewCountThread.interrupt();
}
// mFullNewCountThread = new Thread(() -> {
// Collection collection = getCol();
// TODO: refactor code to not rewrite this query, add to Sched.totalNewForCurrentDeck()
// String query = "SELECT count(*) FROM cards WHERE did IN " +
// Utils.ids2str(collection.getDecks().active()) +
// " AND queue = " + Consts.QUEUE_TYPE_NEW;
// final int fullNewCount = collection.getDb().queryScalar(query);
// if (fullNewCount > 0) {
// Runnable setNewTotalText = new Runnable() {
// @Override
// public void run() {
// mTextNewTotal.setText(String.valueOf(fullNewCount));
// }
// };
// if (!Thread.currentThread().isInterrupted()) {
// mTextNewTotal.post(setNewTotalText);
// }
// }
// });
// mFullNewCountThread.start();
}
// Set total number of cards
// mTextTotal.setText(String.valueOf(totalCards));
double[] data = calculateStat(getCol(), getCol().getDecks().current().optLong("id"));
mNewCardsNum = (int) data[2];
mRevCardsNum = revCards;
mShouldConfigBeforeStudy = mNewCardsNum == totalCards && mShouldConfigBeforeStudy;
int hardNum = getLapses(getCol(), getCol().getDecks().current().optLong("id"));
mDeckListAdapter.mTextCountHandled = String.format(Locale.CHINA, "%d", (int) data[0]);
mDeckListAdapter.mTextCountLearning = String.format(Locale.CHINA, "%d", (int) data[1]);
mDeckListAdapter.mTextCountNew = String.format(Locale.CHINA, "%d", (int) data[2]);
mDeckListAdapter.mTextCountHard = String.format(Locale.CHINA, "%d", hardNum);
mDeckListAdapter.mTextTotal = String.format(Locale.CHINA, "共%d张卡牌", totalCards);
double percent = 0;
if (data[2] == 0) {
// 新卡已学完,显示已掌握
percent = (data[0] + data[1] + data[2] <= 0) ? 0 : (data[0] / (data[0] + data[1] + data[2]) * 100);
mDeckListAdapter.mTextHandledNum = String.format(Locale.CHINA, "%.0f/%.0f", data[0], (data[0] + data[1] + data[2]));
// holder.handled_percent.setText((String.format(Locale.CHINA, "已掌握 %.1f", percent)) + "%");
} else {
percent = (data[0] + data[1] + data[2] <= 0) ? 0 : ((data[0] + data[1]) / (data[0] + data[1] + data[2]) * 100);
mDeckListAdapter.mTextHandledNum = String.format(Locale.CHINA, "%.0f/%.0f", data[0] + data[1], data[0] + data[1] + data[2]);
// holder.handled_percent.setText((String.format(Locale.CHINA, "已学 %.1f", percent)) + "%");
}
// double percent = (data[0] + data[1] + data[2] <= 0) ? 0 : (data[0] / (data[0] + data[1] + data[2]) * 100);
// mStudyProgress.setMax(100*100);
mDeckListAdapter.mStudyProgress = (int) (percent * 100);
mDeckListAdapter.mTextHandledPercent = (String.format(Locale.CHINA, data[2] == 0 ? "已掌握 %.1f" : "已学 %.1f", percent)) + "%";
// Set estimated time remaining
int eta = (newCards + revCards) * 10 / 60;
if ((newCards + revCards) % 60 != 0) {
eta++;
}
if (eta != -1) {
mDeckListAdapter.mTextETA = "" + eta;
} else {
mDeckListAdapter.mTextETA = "-";
}
mDeckListAdapter.notifyDataSetChangedAll();
// Rebuild the options menu
configureToolbar();
}
updateDeckList();
// If in fragmented mode, refresh the deck list
if (mFragmented && refreshDecklist) {
mListener.onRequireDeckListUpdate();
}
}
};
}
use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundImportReplace.
private TaskData doInBackgroundImportReplace(TaskData param) {
Timber.d("doInBackgroundImportReplace");
String path = param.getString();
Resources res = AnkiDroidApp.getInstance().getBaseContext().getResources();
// extract the deck from the zip file
String colPath = CollectionHelper.getCollectionPath(mContext);
File dir = new File(new File(colPath).getParentFile(), "tmpzip");
if (dir.exists()) {
BackupManager.removeDir(dir);
}
// from anki2.py
String colname = "collection.anki21";
ZipFile zip;
try {
zip = new ZipFile(new File(path));
} catch (IOException e) {
Timber.e(e, "doInBackgroundImportReplace - Error while unzipping");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace0");
return new TaskData(false);
}
try {
// v2 scheduler?
if (zip.getEntry(colname) == null) {
colname = CollectionHelper.COLLECTION_FILENAME;
}
Utils.unzipFiles(zip, dir.getAbsolutePath(), new String[] { colname, "media" }, null);
} catch (IOException e) {
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace - unzip");
return new TaskData(false);
}
String colFile = new File(dir, colname).getAbsolutePath();
if (!(new File(colFile)).exists()) {
return new TaskData(false);
}
Collection tmpCol = null;
try {
tmpCol = Storage.Collection(mContext, colFile);
if (!tmpCol.validCollection()) {
tmpCol.close();
return new TaskData(false);
}
} catch (Exception e) {
Timber.e("Error opening new collection file... probably it's invalid");
try {
tmpCol.close();
} catch (Exception e2) {
// do nothing
}
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace - open col");
return new TaskData(false);
} finally {
if (tmpCol != null) {
tmpCol.close();
}
}
publishProgress(new TaskData(res.getString(R.string.importing_collection)));
if (hasValidCol()) {
// unload collection and trigger a backup
Time time = CollectionHelper.getInstance().getTimeSafe(mContext);
CollectionHelper.getInstance().closeCollection(true, "Importing new collection");
CollectionHelper.getInstance().lockCollection();
BackupManager.performBackupInBackground(colPath, true, time);
}
// overwrite collection
File f = new File(colFile);
if (!f.renameTo(new File(colPath))) {
// Exit early if this didn't work
return new TaskData(false);
}
int addedCount = -1;
try {
CollectionHelper.getInstance().unlockCollection();
// because users don't have a backup of media, it's safer to import new
// data and rely on them running a media db check to get rid of any
// unwanted media. in the future we might also want to duplicate this step
// import media
HashMap<String, String> nameToNum = new HashMap<>();
HashMap<String, String> numToName = new HashMap<>();
File mediaMapFile = new File(dir.getAbsolutePath(), "media");
if (mediaMapFile.exists()) {
JsonReader jr = new JsonReader(new FileReader(mediaMapFile));
jr.beginObject();
String name;
String num;
while (jr.hasNext()) {
num = jr.nextName();
name = jr.nextString();
nameToNum.put(name, num);
numToName.put(num, name);
}
jr.endObject();
jr.close();
}
String mediaDir = Media.getCollectionMediaPath(colPath);
int total = nameToNum.size();
int i = 0;
for (Map.Entry<String, String> entry : nameToNum.entrySet()) {
String file = entry.getKey();
String c = entry.getValue();
File of = new File(mediaDir, file);
if (!of.exists()) {
Utils.unzipFiles(zip, mediaDir, new String[] { c }, numToName);
}
++i;
publishProgress(new TaskData(res.getString(R.string.import_media_count, (i + 1) * 100 / total)));
}
zip.close();
// delete tmp dir
BackupManager.removeDir(dir);
return new TaskData(true);
} catch (RuntimeException e) {
Timber.e(e, "doInBackgroundImportReplace - RuntimeException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace1");
return new TaskData(false);
} catch (FileNotFoundException e) {
Timber.e(e, "doInBackgroundImportReplace - FileNotFoundException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace2");
return new TaskData(false);
} catch (IOException e) {
Timber.e(e, "doInBackgroundImportReplace - IOException");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace3");
return new TaskData(false);
}
}
use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundDismissNotes.
private TaskData doInBackgroundDismissNotes(TaskData param) {
Collection col = getCol();
AbstractSched sched = col.getSched();
Object[] data = param.getObjArray();
long[] cardIds = (long[]) data[0];
// query cards
Card[] cards = new Card[cardIds.length];
for (int i = 0; i < cardIds.length; i++) {
cards[i] = col.getCard(cardIds[i]);
}
Collection.DismissType type = (Collection.DismissType) data[1];
try {
col.getDb().getDatabase().beginTransaction();
try {
switch(type) {
case SUSPEND_CARD_MULTI:
{
// collect undo information
long[] cids = new long[cards.length];
boolean[] originalSuspended = new boolean[cards.length];
boolean hasUnsuspended = false;
for (int i = 0; i < cards.length; i++) {
Card card = cards[i];
cids[i] = card.getId();
if (card.getQueue() != Consts.QUEUE_TYPE_SUSPENDED) {
hasUnsuspended = true;
originalSuspended[i] = false;
} else {
originalSuspended[i] = true;
}
}
// otherwise unsuspend all
if (hasUnsuspended) {
sched.suspendCards(cids);
} else {
sched.unsuspendCards(cids);
}
Undoable suspendCardMulti = new UndoSuspendCardMulti(cards, originalSuspended);
// mark undo for all at once
col.markUndo(suspendCardMulti);
// reload cards because they'll be passed back to caller
for (Card c : cards) {
c.load();
}
sched.deferReset();
break;
}
case FLAG:
{
int flag = (Integer) data[2];
col.setUserFlag(flag, cardIds);
for (Card c : cards) {
c.load();
}
break;
}
case MARK_NOTE_MULTI:
{
Set<Note> notes = CardUtils.getNotes(Arrays.asList(cards));
// collect undo information
List<Note> originalMarked = new ArrayList<>();
List<Note> originalUnmarked = new ArrayList<>();
for (Note n : notes) {
if (n.hasTag("marked")) {
originalMarked.add(n);
} else {
originalUnmarked.add(n);
}
}
CardUtils.markAll(new ArrayList<>(notes), !originalUnmarked.isEmpty());
Undoable markNoteMulti = new UndoMarkNoteMulti(originalMarked, originalUnmarked);
// mark undo for all at once
col.markUndo(markNoteMulti);
// reload cards because they'll be passed back to caller
for (Card c : cards) {
c.load();
}
break;
}
case DELETE_NOTE_MULTI:
{
// list of all ids to pass to remNotes method.
// Need Set (-> unique) so we don't pass duplicates to col.remNotes()
Set<Note> notes = CardUtils.getNotes(Arrays.asList(cards));
List<Card> allCards = CardUtils.getAllCards(notes);
// delete note
long[] uniqueNoteIds = new long[notes.size()];
Note[] notesArr = notes.toArray(new Note[notes.size()]);
int count = 0;
for (Note note : notes) {
uniqueNoteIds[count] = note.getId();
count++;
}
Undoable deleteNoteMulti = new UndoDeleteNoteMulti(notesArr, allCards);
col.markUndo(deleteNoteMulti);
col.remNotes(uniqueNoteIds);
sched.deferReset();
// pass back all cards because they can't be retrieved anymore by the caller (since the note is deleted)
publishProgress(new TaskData(allCards.toArray(new Card[allCards.size()])));
break;
}
case CHANGE_DECK_MULTI:
{
long newDid = (long) data[2];
Timber.i("Changing %d cards to deck: '%d'", cards.length, newDid);
Deck deckData = col.getDecks().get(newDid);
if (Decks.isDynamic(deckData)) {
// #5932 - can't change to a dynamic deck. Use "Rebuild"
Timber.w("Attempted to move to dynamic deck. Cancelling task.");
return new TaskData(false);
}
// Confirm that the deck exists (and is not the default)
try {
long actualId = deckData.getLong("id");
if (actualId != newDid) {
Timber.w("Attempted to move to deck %d, but got %d", newDid, actualId);
return new TaskData(false);
}
} catch (Exception e) {
Timber.e(e, "failed to check deck");
return new TaskData(false);
}
long[] changedCardIds = new long[cards.length];
for (int i = 0; i < cards.length; i++) {
changedCardIds[i] = cards[i].getId();
}
col.getSched().remFromDyn(changedCardIds);
long[] originalDids = new long[cards.length];
for (int i = 0; i < cards.length; i++) {
Card card = cards[i];
card.load();
// save original did for undo
originalDids[i] = card.getDid();
// then set the card ID to the new deck
card.setDid(newDid);
Note note = card.note();
note.flush();
// flush card too, in case, did has been changed
card.flush();
}
Undoable changeDeckMulti = new UndoChangeDeckMulti(cards, originalDids);
// mark undo for all at once
col.markUndo(changeDeckMulti);
break;
}
case RESCHEDULE_CARDS:
case REPOSITION_CARDS:
case RESET_CARDS:
{
// collect undo information, sensitive to memory pressure, same for all 3 cases
try {
Timber.d("Saving undo information of type %s on %d cards", type, cards.length);
Card[] cards_copied = deepCopyCardArray(cards);
Undoable repositionRescheduleResetCards = new UndoRepositionRescheduleResetCards(type, cards_copied);
col.markUndo(repositionRescheduleResetCards);
} catch (CancellationException ce) {
Timber.i(ce, "Cancelled while handling type %s, skipping undo", type);
}
switch(type) {
case RESCHEDULE_CARDS:
sched.reschedCards(cardIds, (Integer) data[2], (Integer) data[2]);
break;
case REPOSITION_CARDS:
sched.sortCards(cardIds, (Integer) data[2], 1, false, true);
break;
case RESET_CARDS:
sched.forgetCards(cardIds);
break;
}
// In all cases schedule a new card so Reviewer doesn't sit on the old one
col.reset();
publishProgress(new TaskData(sched.getCard(), 0));
break;
}
}
col.getDb().getDatabase().setTransactionSuccessful();
} finally {
col.getDb().getDatabase().endTransaction();
}
} catch (RuntimeException e) {
Timber.e(e, "doInBackgroundSuspendCard - RuntimeException on suspending card");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSuspendCard");
return new TaskData(false);
}
// (querying the cards again is unnecessarily expensive)
return new TaskData(true, cards);
}
use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.
the class CollectionTask method doInBackgroundConfChange.
private TaskData doInBackgroundConfChange(TaskData param) {
Timber.d("doInBackgroundConfChange");
Collection col = getCol();
Object[] data = param.getObjArray();
Deck deck = (Deck) data[0];
DeckConfig conf = (DeckConfig) data[1];
try {
long newConfId = conf.getLong("id");
// If new config has a different sorting order, reorder the cards
int oldOrder = col.getDecks().getConf(deck.getLong("conf")).getJSONObject("new").getInt("order");
int newOrder = col.getDecks().getConf(newConfId).getJSONObject("new").getInt("order");
if (oldOrder != newOrder) {
switch(newOrder) {
case 0:
col.getSched().randomizeCards(deck.getLong("id"));
break;
case 1:
col.getSched().orderCards(deck.getLong("id"));
break;
}
}
col.getDecks().setConf(deck, newConfId);
col.save();
return new TaskData(true);
} catch (JSONException e) {
return new TaskData(false);
}
}
Aggregations