Skip to content
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

Fixed bug which was causing AnkiDroid API to not list media on cards. #17859

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ujjol1234
Copy link

Purpose / Description

Fixed the bug which was causing AnkiDroid API to not list media on cards. Added a unit test to confirm that media files are listed correctly in the ReviewInfo.

Fixes

Approach

Reimplemented the filesinStr function which was causing the bug.

How Has This Been Tested?

A unit test testMediaFilesAddedCorrectlyInReviewInfo() has been added in the ContentProviderTest.kt to confirm that the MEDIA_FILES attribute of ReviewInfo gets populated correctly.

Checklist

Please, go through these checks before submitting the PR.

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

Copy link

welcome bot commented Jan 22, 2025

First PR! 🚀 We sincerely appreciate that you have taken the time to propose a change to AnkiDroid! Please have patience with us as we are all volunteers - we will get to this as soon as possible.

currentCard: Card,
includeRemote: Boolean = false,
): List<String> {
val l: MutableList<String> = ArrayList()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@david-allison I have reimplemented the old code for the fileinStr from here:

fun filesInStr(mid: Long?, string: String, includeRemote: Boolean = false): List<String> {

This logic was able to extract the image file names but it could not extract the AV tags. My guess is that this is because the Card.renderOutput returns AVTags and it is in the form SoundOrVideoTag(filename). To address this, I added some logic in the function to extract the AVTags as well, and it is now working as expected! Could you please review the changes and let me know if you think this implementation is correct? Any feedback or suggestions for improvement would be greatly appreciated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should follow upstream as close as possible in libanki, unless there's a strong reason not to

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I will try again with the upstream code you provided

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @david-allison,
I have attempted to implement the upstream code and although the logic successfully includes image files in the returned media array, the audio files are missing. From my observation, the logic I initially added for extracting audio files ensures they are included in the media array.
Here's the code I used:

fun filesInStr(
        currentCard: Card,
        includeRemote: Boolean = false,
    ): List<String> {
        val l: MutableList<String> = ArrayList()
        val model = currentCard.noteType(col)
        val renderOutput = currentCard.renderOutput(col)
        val string = renderOutput.questionText + renderOutput.answerText

        var strings: MutableList<String?> = ArrayList()


        if (model!!.isCloze && string.contains("{{c")) {
                // Expand clozes if necessary
                strings = expandClozes(string)
        } else {
                strings.add(string)
        }

        for (s in strings) {
            var s = s
            // Handle LaTeX
            @KotlinCleanup("change to .map { }")
            val svg = model.optBoolean("latexsvg", false)
            s = LaTeX.mungeQA(s!!, col, svg)

            // Extract filenames from the strings using regex patterns
            var m: Matcher
            for (p in REGEXPS) {
                val fnameIdx =
                    when (p) {
                        fSoundRegexps -> 2
                        fImgAudioRegExpU -> 2
                        else -> 3
                    }
                m = p.matcher(s)
                while (m.find()) {
                    val fname = m.group(fnameIdx)!!
                    val isLocal = !fRemotePattern.matcher(fname.lowercase(Locale.getDefault())).find()
                    if (isLocal || includeRemote) {
                        l.add(fname)
                    }
                }
            }
        }

        return l
    }

Could you please advise on how you would like to proceed? Should I enhance the upstream logic to account for audio files, or is there another direction you'd suggest?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't look like the upstream code, which will cause problems.

From a brief glance, the upstream code looks like it handles sound files:

https://github.com/ankitects/anki/blob/56dd93b5be107f7fa8cf78affa22f2e93d06b4ad/pylib/anki/media.py#L33-L44

  • Implement the upstream code, as-is. This should get a subset of sounds or images
  • Write a couple of ignored & failing tests for the functionality which it doesn't cover.
  • We can discuss the exact issues with the failing tests, and see if it's an issue upstream, or an issue with our conversion

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@david-allison I have implemented the upstream python code that you provided:

 fun filesInStr(
        currentCard: Card,
        includeRemote: Boolean = false,
    ): List<String> {
        val files = mutableListOf<String>()
        var string = currentCard.renderOutput(col).questionText + currentCard.renderOutput(col).answerText

        string = LaTeX.mungeQA(string,col,true)

        for (reg in REGEXPS){
            val matcher = reg.matcher(string)
            while (matcher.find()) {
                val fname = matcher.group("fname")?:continue
                val isLocal = !Pattern.compile("(https?|ftp)://", Pattern.CASE_INSENSITIVE)
                    .matcher(fname).find()
                if (isLocal || includeRemote) {
                    files.add(fname)
                }
            }
        }
            return files
    }

The media_files array is now returning an empty result. This might be due to the current LaTeX tags not aligning with those expected by mungeQA(). Kindly let me know how you’d like to proceed.

Copy link
Author

@ujjol1234 ujjol1234 Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @david-allison, just checking in—any updates on my last comment? Thanks!

Copy link
Member

@david-allison david-allison Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you push the code, or provide a patch from Copy patch to clipboard so it can be discussed?

Please follow up in 24 hours if it's not looked at. Still far too busy, but my lack of communication wasn't intentional

@lukstbit lukstbit added the Needs Author Reply Waiting for a reply from the original author label Jan 23, 2025
@@ -1344,6 +1345,57 @@ class ContentProviderTest : InstrumentedTest() {
} ?: fail("query returned null")
}

@Test
fun testMediaFilesAddedCorrectlyInReviewInfo() {
Copy link
Member

@david-allison david-allison Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had time to refactor this a little.

Double tap shift to apply patches from the clipboard

Subject: [PATCH] 
---
Index: AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt	(revision 47b50bb72275d02c5f3459f6c6c9907f6d4242f0)
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt	(date 1738013022332)
@@ -21,6 +21,7 @@
 import android.content.ContentResolver
 import android.content.ContentUris
 import android.content.ContentValues
+import android.database.Cursor
 import android.database.CursorWindow
 import android.net.Uri
 import anki.notetypes.StockNotetype
@@ -53,10 +54,12 @@
 import kotlinx.serialization.json.Json
 import net.ankiweb.rsdroid.exceptions.BackendNotFoundException
 import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.allOf
 import org.hamcrest.Matchers.containsString
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.greaterThan
 import org.hamcrest.Matchers.greaterThanOrEqualTo
+import org.hamcrest.Matchers.hasItem
 import org.json.JSONObject
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -69,6 +72,7 @@
 import org.junit.Test
 import timber.log.Timber
 import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
 import kotlin.test.junit.JUnitAsserter.assertNotNull
 
 /**
@@ -1347,17 +1351,27 @@
 
     @Test
     fun testMediaFilesAddedCorrectlyInReviewInfo() {
-        val frontContent = """<img src="img.jpg"> [sound:test.mp3]"""
         val imageFileName = "img.jpg"
         val audioFileName = "test.mp3"
-        val note = addNoteUsingBasicModel("Hello $frontContent", "backContent")
-        val ord = 0
-        val card = note.cards(col)[ord]
+        addNoteUsingBasicModel("""Hello <img src="$imageFileName"> [sound:$audioFileName]""")
+            .firstCard(col).update {
+                queue = QueueType.New
+                due = col.sched.today
+            }
 
-        card.queue = QueueType.New
-        card.due = col.sched.today
-        col.updateCard(card)
+        queryReviewInfo { cursor ->
+            val media = cursor
+                .getString(cursor.getColumnIndex(FlashCardsContract.ReviewInfo.MEDIA_FILES))
+                .let { Json.decodeFromString<List<String>>(it) }
 
+            assertThat("media files returned", media, allOf(
+                hasItem(imageFileName),
+                hasItem(audioFileName),
+            ))
+        }
+    }
+
+    private fun queryReviewInfo(block: (Cursor) -> Unit) {
         contentResolver
             .query(
                 FlashCardsContract.ReviewInfo.CONTENT_URI,
@@ -1365,34 +1379,9 @@
                 null,
                 null,
                 null,
-            )?. let { cursor ->
-                if (!cursor.moveToFirst()) {
-                    fail("failed")
-                }
-
-                assertNotNull("Cursor should not be null", cursor)
-
-                val mediaFilesArray = cursor.getString(cursor.getColumnIndex(FlashCardsContract.ReviewInfo.MEDIA_FILES))
-
-                var imageFilePresent = false
-                var audioFilePresent = false
-                var allMediaFilesPresent = false
-
-                val media: List<String> = Json.decodeFromString(mediaFilesArray)
-
-                for (ele in media) {
-                    if (ele == imageFileName) {
-                        imageFilePresent = true
-                    }
-                    if (ele == audioFileName) {
-                        audioFilePresent = true
-                    }
-                }
-                if (imageFilePresent and audioFilePresent) {
-                    allMediaFilesPresent = true
-                }
-
-                assertTrue("All media files should be present in the media_files array", allMediaFilesPresent)
+            )?.use { cursor ->
+                assertTrue("has rows") { cursor.moveToFirst() }
+                block(cursor)
             }
     }
 
Index: AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt	(revision 47b50bb72275d02c5f3459f6c6c9907f6d4242f0)
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt	(date 1738013022339)
@@ -149,6 +149,14 @@
         }
     }
 
+
+    @DuplicatedCode("This should be refactored into a shared library later")
+    fun Card.update(block: Card.() -> Unit): Card {
+        block(this)
+        col.updateCard(this)
+        return this
+    }
+
     @DuplicatedCode("This is copied from RobolectricTest. This will be refactored into a shared library later")
     protected fun Card.moveToReviewQueue() {
         this.queue = QueueType.Rev

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @david-allison,

I hope you're doing well! I just wanted to check in again on this PR—it’s been two weeks since my last update, and I haven’t received a reply yet. I wanted to make sure I haven’t missed anything or if there’s anything I should adjust to move this forward.

Please let me know if there are any changes you'd like me to make or if there’s anything else I can do. Thanks for your time, and I really appreciate your feedback!

Copy link
Member

@david-allison david-allison Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I haven't seen any movement on this comment in a while. It still feels to be pending

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Author Reply Waiting for a reply from the original author New contributor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] AnkiDroid API no longer lists media on cards
3 participants