Skip to content

PERF: Use backend to load Note #14948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import com.ichi2.anki.testutil.grantPermissions
import com.ichi2.libanki.*
import com.ichi2.libanki.exception.ConfirmModSchemaException
import com.ichi2.utils.KotlinCleanup
import com.ichi2.utils.emptyStringArray
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
import org.json.JSONObject
import org.junit.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
Expand Down Expand Up @@ -69,7 +69,7 @@ class ContentProviderTest : InstrumentedTest() {
private val mTestDeckIds: MutableList<Long> = ArrayList(TEST_DECKS.size + 1)
private lateinit var mCreatedNotes: ArrayList<Uri>
private var mModelId: Long = 0
private var mDummyFields = arrayOfNulls<String>(1)
private var mDummyFields = emptyStringArray(1)

/**
* Initially create one note for each model.
Expand Down Expand Up @@ -106,12 +106,12 @@ class ContentProviderTest : InstrumentedTest() {
* set-down, */
val did = col.decks.byName(partialName!!)?.id ?: col.decks.id(partialName)
mTestDeckIds.add(did)
mCreatedNotes.add(setupNewNote(col, mModelId, did, mDummyFields.requireNoNulls(), TEST_TAG))
mCreatedNotes.add(setupNewNote(col, mModelId, did, mDummyFields, TEST_TAG))
partialName += "::"
}
}
// Add a note to the default deck as well so that testQueryNextCard() works
mCreatedNotes.add(setupNewNote(col, mModelId, 1, mDummyFields.requireNoNulls(), TEST_TAG))
mCreatedNotes.add(setupNewNote(col, mModelId, 1, mDummyFields, TEST_TAG))
}

/**
Expand Down Expand Up @@ -208,10 +208,10 @@ class ContentProviderTest : InstrumentedTest() {
assertNotNull("check note URI path", newNoteUri!!.lastPathSegment)
val addedNote = Note(col, newNoteUri.lastPathSegment!!.toLong())
addedNote.load()
assertArrayEquals(
assertEquals(
"Check that fields were set correctly",
addedNote.fields,
TEST_NOTE_FIELDS
TEST_NOTE_FIELDS.toMutableList()
)
assertEquals("Check that tag was set correctly", TEST_TAG, addedNote.tags[0])
val model: JSONObject? = col.notetypes.get(mModelId)
Expand Down Expand Up @@ -495,8 +495,7 @@ class ContentProviderTest : InstrumentedTest() {
dummyFields2[0] = TEST_FIELD_VALUE
for (uri in mCreatedNotes) {
// Update the flds
@Suppress("UNCHECKED_CAST")
cv.put(FlashCardsContract.Note.FLDS, Utils.joinFields(dummyFields2 as Array<String>))
cv.put(FlashCardsContract.Note.FLDS, Utils.joinFields(dummyFields2))
cr.update(uri, cv, null, null)
cr.query(uri, FlashCardsContract.Note.DEFAULT_PROJECTION, null, null, null)
.use { noteCursor ->
Expand All @@ -513,10 +512,10 @@ class ContentProviderTest : InstrumentedTest() {
val newFields = Utils.splitFields(
noteCursor.getString(noteCursor.getColumnIndex(FlashCardsContract.Note.FLDS))
)
assertArrayEquals(
assertEquals(
"Check that the flds have been updated correctly",
newFields,
dummyFields2
dummyFields2.toMutableList()
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2053,7 +2053,7 @@ open class CardBrowser :
Column.CARD -> if (inCardMode) card.template().optString("name") else "${card.note().numberOfCards()}"
Column.DUE -> card.dueString
Column.EASE -> if (inCardMode) getEaseForCards() else getAvgEaseForNotes()
Column.CHANGED -> LanguageUtil.getShortDateFormatFromS(if (inCardMode) card.mod else card.note().mod)
Column.CHANGED -> LanguageUtil.getShortDateFormatFromS(if (inCardMode) card.mod else card.note().mod.toLong())
Column.CREATED -> LanguageUtil.getShortDateFormatFromMs(card.note().id)
Column.EDITED -> LanguageUtil.getShortDateFormatFromS(card.note().mod)
Column.INTERVAL -> if (inCardMode) queryIntervalForCards() else queryAvgIntervalForNotes()
Expand Down
7 changes: 4 additions & 3 deletions AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import org.json.JSONObject
import timber.log.Timber
import java.util.*
import java.util.function.Consumer
import kotlin.collections.ArrayList
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
Expand Down Expand Up @@ -139,7 +140,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags

/* Null if adding a new card. Presently NonNull if editing an existing note - but this is subject to change */
private var mCurrentEditedCard: Card? = null
private var mSelectedTags: ArrayList<String>? = null
private var mSelectedTags: MutableList<String>? = null

@get:VisibleForTesting
var deckId: DeckId = 0
Expand Down Expand Up @@ -383,7 +384,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
if (mSelectedTags == null) {
mSelectedTags = ArrayList(0)
}
savedInstanceState.putStringArrayList("tags", mSelectedTags)
savedInstanceState.putStringArrayList("tags", mSelectedTags?.let { ArrayList(it) })
}

private val fieldsAsBundleForPreview: Bundle
Expand Down Expand Up @@ -1524,7 +1525,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
}

private fun setEditFieldTexts(contents: String?) {
var fields: Array<String>? = null
var fields: List<String>? = null
val len: Int
if (contents == null) {
len = 0
Expand Down
60 changes: 24 additions & 36 deletions AnkiDroid/src/main/java/com/ichi2/libanki/Note.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@
package com.ichi2.libanki

import androidx.annotation.VisibleForTesting
import com.ichi2.libanki.exception.WrongId
import com.ichi2.utils.KotlinCleanup
import com.ichi2.utils.emptyStringArray
import com.ichi2.utils.emptyStringMutableList
import net.ankiweb.rsdroid.RustCleanup
import org.json.JSONObject
import timber.log.Timber
import java.util.*
import java.util.regex.Pattern

Expand All @@ -43,17 +42,14 @@ class Note : Cloneable {

var mid: Long = 0
private set
lateinit var tags: ArrayList<String>
lateinit var tags: MutableList<String>
private set
lateinit var fields: Array<String>
lateinit var fields: MutableList<String>
private set
private var mFlags = 0
private var mData: String? = null
private var mFMap: Map<String, Pair<Int, JSONObject>>? = null
private var mScm: Long = 0
var usn = 0
private set
var mod: Long = 0
var mod: Int = 0
private set

constructor(col: Collection, id: Long) {
Expand All @@ -68,36 +64,27 @@ class Note : Cloneable {
guId = Utils.guid64()
this.notetype = notetype
mid = notetype.getLong("id")
tags = ArrayList()
fields = Array(notetype.getJSONArray("flds").length()) { "" }
mFlags = 0
mData = ""
tags = mutableListOf()
fields = emptyStringMutableList(notetype.getJSONArray("flds").length())
mFMap = Notetypes.fieldMap(this.notetype)
mScm = col.scm
}

fun load() {
Timber.d("load()")
col.db
.query(
"SELECT guid, mid, mod, usn, tags, flds, flags, data FROM notes WHERE id = ?",
this.id
).use { cursor ->
if (!cursor.moveToFirst()) {
throw WrongId(this.id, "note")
}
guId = cursor.getString(0)
mid = cursor.getLong(1)
mod = cursor.getLong(2)
usn = cursor.getInt(3)
tags = ArrayList(col.tags.split(cursor.getString(4)))
fields = Utils.splitFields(cursor.getString(5))
mFlags = cursor.getInt(6)
mData = cursor.getString(7)
notetype = col.notetypes.get(mid)!!
mFMap = Notetypes.fieldMap(notetype)
mScm = col.scm
}
val note = col.backend.getNote(this.id)
loadFromBackendNote(note)
}

private fun loadFromBackendNote(note: anki.notes.Note) {
this.id = note.id
this.guId = note.guid
this.mid = note.notetypeId
this.notetype = col.notetypes.get(mid)!! // not in libAnki
this.mod = note.mtimeSecs
this.usn = note.usn
// the lists in the protobuf are NOT mutable, even though they cast to MutableList
this.tags = note.tagsList.toMutableList()
this.fields = note.fieldsList.toMutableList()
this.mFMap = Notetypes.fieldMap(notetype)
}

fun reloadModel() {
Expand Down Expand Up @@ -149,7 +136,8 @@ class Note : Cloneable {
return mFMap!!.keys.toTypedArray()
}

fun values(): Array<String> {
@KotlinCleanup("see if we can make this immutable")
fun values(): MutableList<String> {
return fields
}

Expand Down Expand Up @@ -203,7 +191,7 @@ class Note : Cloneable {
}

fun setTagsFromStr(str: String?) {
tags = ArrayList(col.tags.split(str!!))
tags = col.tags.split(str!!)
}

fun delTag(tag: String?) {
Expand Down
6 changes: 3 additions & 3 deletions AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ object Utils {
}

// TODO ensure manual conversion is correct
fun splitFields(fields: String): Array<String> {
fun splitFields(fields: String): MutableList<String> {
// -1 ensures that we don't drop empty fields at the ends
return fields.split(FIELD_SEPARATOR).toTypedArray()
return fields.split(FIELD_SEPARATOR).toMutableList()
}

/*
Expand Down Expand Up @@ -250,7 +250,7 @@ object Utils {
* @param sortIdx An index of the field
* @return The field at sortIdx, without html media, and the csum of the first field.
*/
fun sfieldAndCsum(fields: Array<String>, sortIdx: Int): Pair<String, Long> {
fun sfieldAndCsum(fields: List<String>, sortIdx: Int): Pair<String, Long> {
val firstStripped = stripHTMLMedia(fields[0])
val sortStripped = if (sortIdx == 0) firstStripped else stripHTMLMedia(fields[sortIdx])
return Pair(sortStripped, fieldChecksumWithoutHtmlMedia(firstStripped))
Expand Down
20 changes: 0 additions & 20 deletions AnkiDroid/src/main/java/com/ichi2/libanki/exception/WrongId.kt

This file was deleted.

3 changes: 3 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ object LanguageUtil {
return DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(Date(s * 1000L))
}

fun getShortDateFormatFromS(s: Int): String =
DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(Date(s * 1000L))

fun getLocaleCompat(resources: Resources): Locale? {
return ConfigurationCompat.getLocales(resources.configuration)[0]
}
Expand Down
2 changes: 2 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/utils/StringUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ fun String.lastIndexOfOrNull(c: Char): Int? =
else -> index
}

fun emptyStringMutableList(size: Int): MutableList<String> = MutableList(size) { "" }

fun emptyStringArray(size: Int): Array<String> = Array(size) { "" }
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package com.ichi2.anki.dialogs

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.R
import com.ichi2.testutils.AnkiAssert
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RecursivePictureMenuTest : RecursivePictureMenuUtilTest() {
@Test
fun removeChild() {
Expand Down
32 changes: 16 additions & 16 deletions AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ class NotetypeTest : JvmTest() {
// add a field
var field: JSONObject? = col.notetypes.newField("foo")
col.notetypes.addField(m, field!!)
assertArrayEquals(
arrayOf("1", "2", ""),
assertEquals(
listOf("1", "2", ""),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -158,8 +158,8 @@ class NotetypeTest : JvmTest() {
assertEquals("", col.getNote(col.notetypes.nids(m)[0]).getItem("bar"))
// delete back
col.notetypes.remField(m, m.getJSONArray("flds").getJSONObject(1))
assertArrayEquals(
arrayOf("1", ""),
assertEquals(
listOf("1", ""),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -168,8 +168,8 @@ class NotetypeTest : JvmTest() {
)
// move 0 -> 1
col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 1)
assertArrayEquals(
arrayOf("", "1"),
assertEquals(
listOf("", "1"),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -178,8 +178,8 @@ class NotetypeTest : JvmTest() {
)
// move 1 -> 0
col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(1), 0)
assertArrayEquals(
arrayOf("1", ""),
assertEquals(
listOf("1", ""),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -192,8 +192,8 @@ class NotetypeTest : JvmTest() {
note = col.getNote(col.notetypes.nids(m)[0])
note.setItem("baz", "2")
note.flush()
assertArrayEquals(
arrayOf("1", "", "2"),
assertEquals(
listOf("1", "", "2"),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -202,8 +202,8 @@ class NotetypeTest : JvmTest() {
)
// move 2 -> 1
col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(2), 1)
assertArrayEquals(
arrayOf("1", "2", ""),
assertEquals(
listOf("1", "2", ""),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -212,8 +212,8 @@ class NotetypeTest : JvmTest() {
)
// move 0 -> 2
col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 2)
assertArrayEquals(
arrayOf("2", "", "1"),
assertEquals(
listOf("2", "", "1"),
col.getNote(
col.notetypes.nids(
m
Expand All @@ -222,8 +222,8 @@ class NotetypeTest : JvmTest() {
)
// move 0 -> 1
col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 1)
assertArrayEquals(
arrayOf("", "2", "1"),
assertEquals(
listOf("", "2", "1"),
col.getNote(
col.notetypes.nids(
m
Expand Down
Loading