From d2be0ea9def656d53b8cb0b09a5698137745e02f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 10 Jun 2022 16:48:04 +1000 Subject: [PATCH 1/5] Don't store collection reference in DeckTreeNode Pass it in to processChildren() instead, which is cleaner, and will work better with the move to a backend method. --- .../libanki/sched/AbstractDeckTreeNode.kt | 3 +- .../ichi2/libanki/sched/DeckDueTreeNode.kt | 4 +-- .../com/ichi2/libanki/sched/DeckTreeNode.kt | 8 +++--- .../java/com/ichi2/libanki/sched/Sched.java | 2 +- .../java/com/ichi2/libanki/sched/SchedV2.java | 6 ++-- .../com/ichi2/libanki/sched/SchedV2Test.java | 28 +++++++++---------- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractDeckTreeNode.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractDeckTreeNode.kt index a448ce44080a..3237e7fe1da9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractDeckTreeNode.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractDeckTreeNode.kt @@ -32,7 +32,6 @@ import java.util.* * [processChildren] should be called if the children of this node are modified. */ abstract class AbstractDeckTreeNode( - val col: Collection, /** * @return The full deck name, e.g. "A::B::C" */ @@ -68,7 +67,7 @@ abstract class AbstractDeckTreeNode( ) } - abstract fun processChildren(children: List, addRev: Boolean) + abstract fun processChildren(col: Collection, children: List, addRev: Boolean) override fun toString(): String { val buf = StringBuffer() diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt index ac519b58b071..c3d5d445f976 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt @@ -34,7 +34,7 @@ import kotlin.math.min */ @KotlinCleanup("maybe possible to remove gettres for revCount/lrnCount") @KotlinCleanup("rename name -> fullDeckName") -class DeckDueTreeNode(col: Collection, name: String, did: Long, override var revCount: Int, override var lrnCount: Int, override var newCount: Int) : AbstractDeckTreeNode(col, name, did) { +class DeckDueTreeNode(name: String, did: Long, override var revCount: Int, override var lrnCount: Int, override var newCount: Int) : AbstractDeckTreeNode(name, did) { override fun toString(): String { return String.format( Locale.US, "%s, %d, %d, %d, %d", @@ -50,7 +50,7 @@ class DeckDueTreeNode(col: Collection, name: String, did: Long, override var rev newCount = max(0, min(newCount, limit)) } - override fun processChildren(children: List, addRev: Boolean) { + override fun processChildren(col: Collection, children: List, addRev: Boolean) { // tally up children counts for (ch in children) { lrnCount += ch.lrnCount diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckTreeNode.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckTreeNode.kt index e6ba57952163..82194c2cbbc8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckTreeNode.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckTreeNode.kt @@ -16,11 +16,11 @@ package com.ichi2.libanki.sched import com.ichi2.libanki.Collection -import com.ichi2.utils.KotlinCleanup +import net.ankiweb.rsdroid.RustCleanup -@KotlinCleanup("confusing nullability for col, verify real nullability after code related to scheduling is fully migrated to kotlin") -class DeckTreeNode(col: Collection?, name: String, did: Long) : AbstractDeckTreeNode(col!!, name, did) { - override fun processChildren(children: List, addRev: Boolean) { +@RustCleanup("processChildren() can be removed after migrating to backend implementation") +class DeckTreeNode(name: String, did: Long) : AbstractDeckTreeNode(name, did) { + override fun processChildren(col: Collection, children: List, addRev: Boolean) { // intentionally blank } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java index 64e013bf3432..2905b4aa2070 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java @@ -242,7 +242,7 @@ private void unburyCardsForDeck(@NonNull List allDecks) { // reviews int rev = _revForDeck(deck.getLong("id"), rlim); // save to list - deckNodes.add(new DeckDueTreeNode(mCol, deck.getString("name"), deck.getLong("id"), rev, lrn, _new)); + deckNodes.add(new DeckDueTreeNode(deck.getString("name"), deck.getLong("id"), rev, lrn, _new)); // add deck as a parent lims.put(Decks.normalizeName(deck.getString("name")), new Integer[]{nlim, rlim}); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java index 59b2e85c9ae3..7e6d5bc0cc67 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java @@ -560,7 +560,7 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt int rlim = _deckRevLimitSingle(deck, plim, false); int rev = _revForDeck(deck.getLong("id"), rlim, childMap); // save to list - deckNodes.add(new DeckDueTreeNode(mCol, deck.getString("name"), deck.getLong("id"), rev, lrn, _new)); + deckNodes.add(new DeckDueTreeNode(deck.getString("name"), deck.getLong("id"), rev, lrn, _new)); // add deck as a parent lims.put(Decks.normalizeName(deck.getString("name")), new Integer[]{nlim, rlim}); } @@ -579,7 +579,7 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt // Similar to deckDueList ArrayList allDecksSorted = new ArrayList<>(); for (JSONObject deck : mCol.getDecks().allSorted()) { - DeckTreeNode g = new DeckTreeNode(mCol, deck.getString("name"), deck.getLong("id")); + DeckTreeNode g = new DeckTreeNode(deck.getString("name"), deck.getLong("id")); allDecksSorted.add(g); } // End of the similar part. @@ -665,7 +665,7 @@ public List> deckDueTree(@Nullable CancelListener canc TreeNode toAdd = new TreeNode<>(child); toAdd.getChildren().addAll(childrenNode); List childValues = childrenNode.stream().map(TreeNode::getValue).collect(Collectors.toList()); - child.processChildren(childValues, "std".equals(getName())); + child.processChildren(mCol, childValues, "std".equals(getName())); sortedChildren.add(toAdd); } diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java index 08c209bcea2a..1b93fea9ebfc 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java @@ -109,16 +109,16 @@ protected static List> expectedTree(Collection col, bo // These matched the previous Java data // These may want to be changed back List> expected = new ArrayList<>(); - DeckDueTreeNode caz = new DeckDueTreeNode(col, "cmxieunwoogyxsctnjmv::abcdefgh::ZYXW", 1596783600480L, 0, 0, 0); - DeckDueTreeNode ca = new DeckDueTreeNode(col, "cmxieunwoogyxsctnjmv::abcdefgh", 1596783600460L, 0, 0, 0); - DeckDueTreeNode ci = new DeckDueTreeNode(col, "cmxieunwoogyxsctnjmv::INSBGDS", 1596783600500L, 0, 0, 0); - DeckDueTreeNode c = new DeckDueTreeNode(col, "cmxieunwoogyxsctnjmv", 1596783600440L, 0, 0, 0); - DeckDueTreeNode defaul = new DeckDueTreeNode(col, "Default", 1, 0, 0, 0); - DeckDueTreeNode s = new DeckDueTreeNode(col, "scxipjiyozczaaczoawo", 1596783600420L, 0, 0, 0); - DeckDueTreeNode f = new DeckDueTreeNode(col, "blank::foobar", 1596783600540L, 0, 0, 0); - DeckDueTreeNode b = new DeckDueTreeNode(col, "blank", 1596783600520L, 0, 0, 0); - DeckDueTreeNode aBlank = new DeckDueTreeNode(col, "A::blank", 1596783600580L, 0, 0, 0); - DeckDueTreeNode a = new DeckDueTreeNode(col, "A", 1596783600560L, 0, 0, 0); + DeckDueTreeNode caz = new DeckDueTreeNode("cmxieunwoogyxsctnjmv::abcdefgh::ZYXW", 1596783600480L, 0, 0, 0); + DeckDueTreeNode ca = new DeckDueTreeNode("cmxieunwoogyxsctnjmv::abcdefgh", 1596783600460L, 0, 0, 0); + DeckDueTreeNode ci = new DeckDueTreeNode("cmxieunwoogyxsctnjmv::INSBGDS", 1596783600500L, 0, 0, 0); + DeckDueTreeNode c = new DeckDueTreeNode("cmxieunwoogyxsctnjmv", 1596783600440L, 0, 0, 0); + DeckDueTreeNode defaul = new DeckDueTreeNode("Default", 1, 0, 0, 0); + DeckDueTreeNode s = new DeckDueTreeNode("scxipjiyozczaaczoawo", 1596783600420L, 0, 0, 0); + DeckDueTreeNode f = new DeckDueTreeNode("blank::foobar", 1596783600540L, 0, 0, 0); + DeckDueTreeNode b = new DeckDueTreeNode("blank", 1596783600520L, 0, 0, 0); + DeckDueTreeNode aBlank = new DeckDueTreeNode("A::blank", 1596783600580L, 0, 0, 0); + DeckDueTreeNode a = new DeckDueTreeNode("A", 1596783600560L, 0, 0, 0); TreeNode cazNode = new TreeNode<>(caz); @@ -132,7 +132,7 @@ protected static List> expectedTree(Collection col, bo // add "caz" to "ca" caNode.getChildren().add(cazNode); - caNode.getValue().processChildren(Collections.singletonList(cazNode.getValue()), addRev); + caNode.getValue().processChildren(col, Collections.singletonList(cazNode.getValue()), addRev); // add "ca" and "ci" to "c" cNode.getChildren().add(caNode); @@ -140,15 +140,15 @@ protected static List> expectedTree(Collection col, bo ArrayList cChildren = new ArrayList<>(); cChildren.add(caNode.getValue()); cChildren.add(ciNode.getValue()); - cNode.getValue().processChildren(cChildren, addRev); + cNode.getValue().processChildren(col, cChildren, addRev); // add "f" to "b" bNode.getChildren().add(fNode); - bNode.getValue().processChildren(Collections.singletonList(fNode.getValue()), addRev); + bNode.getValue().processChildren(col, Collections.singletonList(fNode.getValue()), addRev); // add "A::" to "A" aNode.getChildren().add(aBlankNode); - aNode.getValue().processChildren(Collections.singletonList(aBlankNode.getValue()), addRev); + aNode.getValue().processChildren(col, Collections.singletonList(aBlankNode.getValue()), addRev); expected.add(aNode); expected.add(bNode); From 545ae1bc6762b26707cf206785f4b097c98371ac Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 11 Jun 2022 15:56:18 +1000 Subject: [PATCH 2/5] Use backend to generate deck list for V1 scheduler Currently limited to V1 due to changes in 2.1.41 --- .../com/ichi2/libanki/backend/DroidBackend.kt | 8 +++++ .../ichi2/libanki/backend/RustDroidBackend.kt | 11 ++++++ .../libanki/backend/RustDroidV16Backend.kt | 35 +++++++++++++++++++ .../ichi2/libanki/sched/DeckDueTreeNode.kt | 2 ++ .../java/com/ichi2/libanki/sched/SchedV2.java | 11 ++++++ 5 files changed, 67 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt index 0235bf5c2ba0..f18edad7bbc0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackend.kt @@ -15,6 +15,7 @@ */ package com.ichi2.libanki.backend +import BackendProto.Backend import BackendProto.Backend.ExtractAVTagsOut import BackendProto.Backend.RenderCardOut import android.content.Context @@ -26,6 +27,8 @@ import com.ichi2.libanki.Decks import com.ichi2.libanki.TemplateManager.TemplateRenderContext import com.ichi2.libanki.backend.exception.BackendNotSupportedException import com.ichi2.libanki.backend.model.SchedTimingToday +import com.ichi2.libanki.sched.DeckDueTreeNode +import com.ichi2.libanki.sched.TreeNode import com.ichi2.utils.KotlinCleanup import net.ankiweb.rsdroid.RustV1Cleanup @@ -84,4 +87,9 @@ interface DroidBackend { @Throws(BackendNotSupportedException::class) fun renderCardForTemplateManager(templateRenderContext: TemplateRenderContext): RenderCardOut + + fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode + + @KotlinCleanup("move to SchedV2 once it's converted to Kotlin") + fun legacyDeckDueTree(includeCounts: Boolean): List> } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt index ea2cde6d48bc..391ab449e431 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidBackend.kt @@ -16,6 +16,7 @@ package com.ichi2.libanki.backend +import BackendProto.Backend import BackendProto.Backend.ExtractAVTagsOut import BackendProto.Backend.RenderCardOut import android.content.Context @@ -25,6 +26,8 @@ import com.ichi2.libanki.TemplateManager.TemplateRenderContext import com.ichi2.libanki.backend.exception.BackendNotSupportedException import com.ichi2.libanki.backend.model.SchedTimingToday import com.ichi2.libanki.backend.model.SchedTimingTodayProto +import com.ichi2.libanki.sched.DeckDueTreeNode +import com.ichi2.libanki.sched.TreeNode import net.ankiweb.rsdroid.BackendFactory import net.ankiweb.rsdroid.database.RustV11SQLiteOpenHelperFactory @@ -94,6 +97,14 @@ open class RustDroidBackend( throw BackendNotSupportedException() } + override fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode { + TODO("Not yet implemented") + } + + override fun legacyDeckDueTree(includeCounts: Boolean): List> { + TODO("Not yet implemented") + } + companion object { const val UNUSED_VALUE = 0 } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidV16Backend.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidV16Backend.kt index 8b994c222063..5c5d7811ee5b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidV16Backend.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/RustDroidV16Backend.kt @@ -23,6 +23,9 @@ import com.ichi2.libanki.DB import com.ichi2.libanki.TemplateManager import com.ichi2.libanki.backend.BackendUtils.to_json_bytes import com.ichi2.libanki.backend.model.to_backend_note +import com.ichi2.libanki.sched.DeckDueTreeNode +import com.ichi2.libanki.sched.TreeNode +import com.ichi2.libanki.utils.TimeManager import com.ichi2.utils.JSONObject import net.ankiweb.rsdroid.BackendFactory import net.ankiweb.rsdroid.BackendV1 @@ -71,4 +74,36 @@ class RustDroidV16Backend(private val backendFactory: BackendFactory) : RustDroi backend.renderExistingCard(templateRenderContext._card.id, templateRenderContext._browser) } } + + override fun deckDueTree(includeCounts: Boolean): Backend.DeckTreeNode { + val now = if (includeCounts) { + TimeManager.time.intTime() + } else { + 0 // counts are skipped + } + return backend.deckTree(now, 0) + } + + override fun legacyDeckDueTree(includeCounts: Boolean): List> { + fun toLegacyNode(node: Backend.DeckTreeNode, parentName: String): TreeNode { + val thisName = if (parentName.isEmpty()) { + node.name + } else { + "$parentName::${node.name}" + } + val treeNode = TreeNode( + DeckDueTreeNode( + thisName, + node.deckId, + node.reviewCount, + node.learnCount, + node.newCount, + ) + ) + treeNode.children.addAll(node.childrenList.asSequence().map { toLegacyNode(it, thisName) }) + return treeNode + } + val top = deckDueTree(includeCounts) + return toLegacyNode(top, "").children + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt index c3d5d445f976..7e3de4f9b7b6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/DeckDueTreeNode.kt @@ -18,6 +18,7 @@ package com.ichi2.libanki.sched import com.ichi2.libanki.Collection import com.ichi2.libanki.Decks import com.ichi2.utils.KotlinCleanup +import net.ankiweb.rsdroid.RustCleanup import java.util.* import kotlin.math.max import kotlin.math.min @@ -34,6 +35,7 @@ import kotlin.math.min */ @KotlinCleanup("maybe possible to remove gettres for revCount/lrnCount") @KotlinCleanup("rename name -> fullDeckName") +@RustCleanup("after migration, consider dropping this and using backend tree structure directly") class DeckDueTreeNode(name: String, did: Long, override var revCount: Int, override var lrnCount: Int, override var newCount: Int) : AbstractDeckTreeNode(name, did) { override fun toString(): String { return String.format( diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java index 7e6d5bc0cc67..512c2dff4c9e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java @@ -31,6 +31,7 @@ import android.text.style.StyleSpan; import android.util.Pair; +import com.ichi2.anki.AnkiDroidApp; import com.ichi2.anki.R; import com.ichi2.async.CancelListener; import com.ichi2.async.CollectionTask; @@ -593,7 +594,17 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt } @Nullable + @RustCleanup("enable for v2 once backend is updated to 2.1.41+") + @RustCleanup("once both v1 and v2 are using backend, cancelListener can be removed") public List> deckDueTree(@Nullable CancelListener cancelListener) { + if (AnkiDroidApp.TESTING_USE_V16_BACKEND && this instanceof Sched) { + // The 2.1.34 backend code can't be used for V2 at the moment, because + // the deck list count handling changed in 2.1.41, and test_review_limits + // expects the newer behaviour (which was already ported to AnkiDroid). + // So we only use the backend to calculate V1 at the moment. + return mCol.getBackend().legacyDeckDueTree(true); + } + List allDecksSorted = deckDueList(cancelListener); if (allDecksSorted == null) { return null; From 15453694356f24c75226ad142b73730337f8d53c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 11 Jun 2022 16:00:53 +1000 Subject: [PATCH 3/5] Enable backend deck list code for v2 scheduler This requires https://github.com/david-allison/anki/pull/2 to be merged into a new AnkiDroid backend release before this can be used. --- .../java/com/ichi2/libanki/sched/SchedV2.java | 18 +++++++----------- .../com/ichi2/libanki/sched/SchedV2Test.java | 10 +++++++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java index 512c2dff4c9e..2595c965e51d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java @@ -597,19 +597,15 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt @RustCleanup("enable for v2 once backend is updated to 2.1.41+") @RustCleanup("once both v1 and v2 are using backend, cancelListener can be removed") public List> deckDueTree(@Nullable CancelListener cancelListener) { - if (AnkiDroidApp.TESTING_USE_V16_BACKEND && this instanceof Sched) { - // The 2.1.34 backend code can't be used for V2 at the moment, because - // the deck list count handling changed in 2.1.41, and test_review_limits - // expects the newer behaviour (which was already ported to AnkiDroid). - // So we only use the backend to calculate V1 at the moment. + if (AnkiDroidApp.TESTING_USE_V16_BACKEND) { return mCol.getBackend().legacyDeckDueTree(true); + } else { + List allDecksSorted = deckDueList(cancelListener); + if (allDecksSorted == null) { + return null; + } + return _groupChildren(allDecksSorted, true); } - - List allDecksSorted = deckDueList(cancelListener); - if (allDecksSorted == null) { - return null; - } - return _groupChildren(allDecksSorted, true); } /** diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java index 1b93fea9ebfc..cd634a678cb6 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java @@ -812,8 +812,12 @@ public void test_review_limits() throws Exception { c.flush(); } - // position 0 is default deck. Different from upstream - TreeNode tree = col.getSched().deckDueTree().get(1); + int parentIndex = 0; + if (!AnkiDroidApp.TESTING_USE_V16_BACKEND) { + // position 0 is default deck. Different from upstream + parentIndex = 1; + } + TreeNode tree = col.getSched().deckDueTree().get(parentIndex); // (('parent', 1514457677462, 5, 0, 0, (('child', 1514457677463, 5, 0, 0, ()),))) assertEquals("parent", tree.getValue().getFullDeckName()); assertEquals(5, tree.getValue().getRevCount()); // paren, tree.review_count)t @@ -829,7 +833,7 @@ public void test_review_limits() throws Exception { col.getSched().answerCard(c, BUTTON_THREE); assertEquals(new Counts(0, 0, 9), col.getSched().counts()); - tree = col.getSched().deckDueTree().get(1); + tree = col.getSched().deckDueTree().get(parentIndex); assertEquals(4, tree.getValue().getRevCount()); assertEquals(9, tree.getChildren().get(0).getValue().getRevCount()); } From 00bcc9dc3fb70552db6f4c38594eac21f0c70e46 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 11 Jun 2022 16:43:04 +1000 Subject: [PATCH 4/5] Make deckDueList() private; switch callers to use deckDueTree() The backend only provides a deckDueTree(), and switching the calls to deckDueList() now will make migration easier in the future. --- .../anki/provider/CardContentProvider.kt | 48 ++++++++++++------- .../com/ichi2/libanki/sched/AbstractSched.kt | 5 -- .../java/com/ichi2/libanki/sched/Sched.java | 2 +- .../java/com/ichi2/libanki/sched/SchedV2.java | 4 +- .../java/com/ichi2/libanki/sync/Syncer.java | 2 +- .../libanki/sched/AbstractSchedTest.java | 2 +- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.kt b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.kt index c2acecabd3fa..5d7bb6aeba43 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.kt @@ -37,6 +37,7 @@ import com.ichi2.libanki.backend.exception.DeckRenameException import com.ichi2.libanki.exception.EmptyMediaException import com.ichi2.libanki.sched.AbstractSched import com.ichi2.libanki.sched.DeckDueTreeNode +import com.ichi2.libanki.sched.TreeNode import com.ichi2.libanki.utils.TimeManager import com.ichi2.utils.FileUtil.internalizeUri import com.ichi2.utils.JSONArray @@ -375,28 +376,44 @@ class CardContentProvider : ContentProvider() { rv } DECKS -> { - val allDecks = col.sched.deckDueList() val columns = projection ?: FlashCardsContract.Deck.DEFAULT_PROJECTION - val rv = MatrixCursor(columns, allDecks.size) - for (deck: DeckDueTreeNode? in allDecks) { - val id = deck!!.did - val name = deck.fullDeckName - addDeckToCursor(id, name, getDeckCountsFromDueTreeNode(deck), rv, col, columns) + val allDecks = col.sched.deckDueTree() + val rv = MatrixCursor(columns, 1) + fun forEach(nodeList: List>, fn: (DeckDueTreeNode) -> Unit) { + for (node in nodeList) { + fn(node.value) + forEach(node.children, fn) + } + } + forEach(allDecks) { + addDeckToCursor( + it.did, + it.fullDeckName, + getDeckCountsFromDueTreeNode(it), + rv, + col, + columns + ) } rv } DECKS_ID -> { - /* Direct access deck */ val columns = projection ?: FlashCardsContract.Deck.DEFAULT_PROJECTION val rv = MatrixCursor(columns, 1) - val allDecks = col.sched.deckDueList() - val deckId = uri.pathSegments[1].toLong() - for (deck: DeckDueTreeNode? in allDecks) { - if (deck!!.did == deckId) { - addDeckToCursor(deckId, deck.fullDeckName, getDeckCountsFromDueTreeNode(deck), rv, col, columns) - return rv + val allDecks = col.sched.deckDueTree() + val desiredDeckId = uri.pathSegments[1].toLong() + fun find(nodeList: List>, id: Long): DeckDueTreeNode? { + for (node in nodeList) { + if (node.value.did == id) { + return node.value + } + return find(node.children, id) } + return null + } + find(allDecks, desiredDeckId)?.let { + addDeckToCursor(it.did, it.fullDeckName, getDeckCountsFromDueTreeNode(it), rv, col, columns) } rv } @@ -413,10 +430,9 @@ class CardContentProvider : ContentProvider() { } } - private fun getDeckCountsFromDueTreeNode(deck: DeckDueTreeNode?): JSONArray { - @KotlinCleanup("use a scope function") + private fun getDeckCountsFromDueTreeNode(deck: DeckDueTreeNode): JSONArray { val deckCounts = JSONArray() - deckCounts.put(deck!!.lrnCount) + deckCounts.put(deck.lrnCount) deckCounts.put(deck.revCount) deckCounts.put(deck.newCount) return deckCounts diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt index da25a91dd3ee..1fd491845bcc 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt @@ -178,11 +178,6 @@ abstract class AbstractSched { */ abstract fun extendLimits(newc: Int, rev: Int) - /** - * @return [deckname, did, rev, lrn, new] - */ - abstract fun deckDueList(): List - /** * @param cancelListener A task that is potentially cancelled * @return the due tree. null if task is cancelled diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java index 2905b4aa2070..4bc262b95c2e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/Sched.java @@ -213,7 +213,7 @@ private void unburyCardsForDeck(@NonNull List allDecks) { * Returns [deckname, did, rev, lrn, new] */ @Override - public @Nullable List deckDueList(@Nullable CancelListener cancelListener) { + protected @Nullable List deckDueList(@Nullable CancelListener cancelListener) { _checkDay(); mCol.getDecks().checkIntegrity(); List allDecksSorted = mCol.getDecks().allSorted(); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java index 2595c965e51d..3d916e76387d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java @@ -523,14 +523,14 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt * * Return nulls when deck task is cancelled. */ - public @NonNull List deckDueList() { + private @NonNull List deckDueList() { return deckDueList(null); } // Overridden /** * Return sorted list of all decks.*/ - public @Nullable List deckDueList(@Nullable CancelListener collectionTask) { + protected @Nullable List deckDueList(@Nullable CancelListener collectionTask) { _checkDay(); mCol.getDecks().checkIntegrity(); List allDecksSorted = mCol.getDecks().allSorted(); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Syncer.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Syncer.java index 935124e61d0a..9167a0d9e801 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Syncer.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Syncer.java @@ -449,7 +449,7 @@ public JSONObject sanityCheck() { mCol.getModels().save(); } // check for missing parent decks - mCol.getSched().deckDueList(); + mCol.getSched().quickDeckDueTree(); // return summary of deck JSONArray check = new JSONArray(); JSONArray counts = new JSONArray(); diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/AbstractSchedTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/AbstractSchedTest.java index b1b1430963c0..ae8f788ac2ad 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/AbstractSchedTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/AbstractSchedTest.java @@ -204,7 +204,7 @@ public void deckDueTreeInconsistentDecksPasses() { addDeckWithExactName(child); getCol().getDecks().checkIntegrity(); - assertDoesNotThrow(() -> getCol().getSched().deckDueList()); + assertDoesNotThrow(() -> getCol().getSched().deckDueTree()); } From 7a6a92c545624f57474789aec490bcd353a6bb62 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 11 Jun 2022 17:25:12 +1000 Subject: [PATCH 5/5] Use backend for quickDeckDueTree() --- .../src/main/java/com/ichi2/libanki/sched/AbstractSched.kt | 2 +- .../src/main/java/com/ichi2/libanki/sched/SchedV2.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt index 1fd491845bcc..f5c4630d1b3d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt @@ -192,7 +192,7 @@ abstract class AbstractSched { /** * @return The tree of decks, without numbers */ - abstract fun quickDeckDueTree(): List> + abstract fun quickDeckDueTree(): List> /** New count for a single deck. * @param did The deck to consider (descendants and ancestors are ignored) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java index 3d916e76387d..8f70672663d8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/SchedV2.java @@ -574,8 +574,11 @@ protected int _walkingCount(@NonNull LimitMethod limFn, @NonNull CountMethod cnt requires multiple database access by deck. Ignoring this number lead to the creation of a tree more quickly.*/ @Override - public @NonNull List> quickDeckDueTree() { - // Similar to deckDueTree, ignoring the numbers + public @NonNull + List> quickDeckDueTree() { + if (AnkiDroidApp.TESTING_USE_V16_BACKEND) { + return mCol.getBackend().legacyDeckDueTree(false); + } // Similar to deckDueList ArrayList allDecksSorted = new ArrayList<>();