FSRS4Anki Helper conflicts when other addon adjust settings

Hi,

I wrote addon (not published) but it goes in conflict with FSRS4Anki Helper. Can you advise how to improve my code?

Anki error:

Anki 23.12.1 (1a1d4d54)  (ao)
Python 3.9.15 Qt 6.6.1 PyQt 6.6.1
Platform: Windows-10-10.0.22631

Traceback (most recent call last):
  File "aqt.taskman", line 142, in _on_closures_pending
  File "aqt.taskman", line 86, in <lambda>
  File "aqt.taskman", line 106, in wrapped_done
  File "aqt.operations", line 127, in wrapped_done
  File "aqt.reviewer", line 537, in after_answer
  File "aqt.reviewer", line 547, in _after_answering
  File "_aqt.hooks", line 4173, in __call__
  File "C:\Users\szare\AppData\Roaming\Anki2\addons21\759844606\schedule\disperse_siblings.py", line 229, in disperse_siblings_when_review
    mw.col.merge_undo_entries(undo_entry)
  File "anki.collection", line 1021, in merge_undo_entries
  File "anki._backend_generated", line 255, in merge_undo_entries
  File "anki._backend", line 166, in _run_command
anki.errors.InvalidInput: target undo op not found

===Add-ons (active)===
(add-on provided name [Add-on folder, installed at, version, is config changed])
Auto Adjuster ['!AutoAdjuster', 0, 'None', '']
FSRS4Anki Helper ['759844606', 2024-01-06T08:46, 'None', mod]

===IDs of active AnkiWeb add-ons===
759844606

===Add-ons (inactive)===
(add-on provided name [Add-on folder, installed at, version, is config changed])
'' ['edit', 0, 'None', '']
AwesomeTTS - Add speech to your flashcards ['1436550454', 2023-11-29T14:54, 'None', '']
Partial import from apkg file mizmu ['331704845', 2023-03-24T03:08, 'None', mod]

When problem appear: when my addon try to edit config of the deck (always when review/relearn goes to 0 but not only):

    def setNewCount(self, newCount: int):
        if self.rawData["new"]["perDay"] != newCount:
            self.rawData["new"]["perDay"] = newCount
            mw.col.decks.setConf(self.rawData, self.id)
            logger.info(f"[{self.name}] value {newCount} of new cards per day has been saved to the configuration.")
            self.update()
//[...]
def reviewer_did_answer_card(reviewer: Reviewer, card: Card, ease: Literal[1, 2, 3, 4]):
    manager: Manager = Manager()
    manager.update(onlyDeckID=[card.did])
    reviewer.refresh_if_needed()

What is the issue?

Full code:

from aqt import gui_hooks
import logging
from aqt import mw
from aqt.reviewer import Reviewer
import json
import os
from anki.cards import Card
from typing import Literal

logger = logging.getLogger(__name__)
FORMAT = "%(asctime)s [%(filename)s][%(funcName)s:%(lineno)s][%(levelname)s]: %(message)s"
logFilePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "auto_adjuster.log")
file_handler = logging.FileHandler(logFilePath, mode="a")
formatter = logging.Formatter(FORMAT)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.setLevel(logging.DEBUG)
logger.info("#")
logger.info("####################################################################################")
logger.info("#")


class Deck:
    def __init__(self, rawData, newLimit) -> None:
        self.rawData = rawData
        self.newLimit = newLimit
        self.update()

    def update(self):
        self.id = self.rawData["id"]
        self.name = self.rawData["name"]
        self.configID = self.rawData["conf"]

    def getPrettyConfigData(self):
        return json.dumps(self.rawData, indent=4)

    def getCountCardsCountDoneToday(self):
        query = f"deck:{self.name} rated:1"
        cardsCount = len(mw.col.find_cards(query))
        return cardsCount

    def getCountStillInQueue(self):
        query = f"deck:{self.name} is:due"
        cardsCount = len(mw.col.find_cards(query))
        return cardsCount


class Config:
    def __init__(self, rawData) -> None:
        self.rawData = rawData
        self.update()

    def update(self):
        self.id = self.rawData["id"]
        self.name = self.rawData["name"]

    def getPrettyConfigData(self):
        return json.dumps(self.rawData, indent=4)

    def setNewCount(self, newCount: int):
        if self.rawData["new"]["perDay"] != newCount:
            self.rawData["new"]["perDay"] = newCount
            mw.col.decks.setConf(self.rawData, self.id)
            logger.info(f"[{self.name}] value {newCount} of new cards per day has been saved to the configuration.")
            self.update()


class Manager:
    def __init__(self) -> None:
        self.decks = []
        self.configs = []

    def update(self, onlyDeckID: list[int] = []):
        self.decks: list[Deck] = self.getDecks(onlyDeckID=onlyDeckID)
        self.configs = self.getConfigs()

        for deck in self.decks:
            deckConfig: Config = None
            for config in self.configs:
                if config.id == deck.configID:
                    deckConfig = config
                    break
            self.setNewCardsCount(deck=deck, deckConfig=deckConfig)

    def getDecks(self, onlyDeckID: list[int] = []):
        decksList = []
        rawDecks = mw.col.decks.get_all_legacy()
        allowedDecks = mw.addonManager.getConfig(__name__)["includedDecks"]
        for rawDeck in rawDecks:
            if onlyDeckID == []:
                if rawDeck["name"] in allowedDecks:
                    deck = Deck(rawData=rawDeck, newLimit=allowedDecks[rawDeck["name"]])
                    decksList.append(deck)
            else:
                if rawDeck["id"] in onlyDeckID and rawDeck["name"] in allowedDecks:
                    deck = Deck(rawData=rawDeck, newLimit=allowedDecks[rawDeck["name"]])
                    decksList.append(deck)
        return decksList

    def getConfigs(self):
        configList = []
        rawConfigs = mw.col.decks.all_config()
        for rawConfig in rawConfigs:
            if rawConfig["id"] != 1:
                config = Config(rawData=rawConfig)
                configList.append(config)
        return configList

    def setNewCardsCount(self, deck: Deck, deckConfig: Config):
        countCardsCountDoneToday = deck.getCountCardsCountDoneToday()
        countStillInQueue = deck.getCountStillInQueue()
        logger.debug(f"[{deckConfig.name}][{deck.name}] countCardsCountDoneToday {countCardsCountDoneToday} countStillInQueue {countStillInQueue} newLimit {deck.newLimit}")
        if countStillInQueue != 0:
            deckConfig.setNewCount(newCount=0)
            return
        if countCardsCountDoneToday < deck.newLimit:
            deckConfig.setNewCount(newCount=100)
        else:
            deckConfig.setNewCount(newCount=1)


def profile_did_open():
    manager: Manager = Manager()
    manager.update()


def reviewer_did_answer_card(reviewer: Reviewer, card: Card, ease: Literal[1, 2, 3, 4]):
    manager: Manager = Manager()
    manager.update(onlyDeckID=[card.did])
    reviewer.refresh_if_needed()


gui_hooks.profile_did_open.append(profile_did_open)
gui_hooks.reviewer_did_answer_card.append(reviewer_did_answer_card)

I guess your code breaks the undo steps of FSRS helper add-on. For details, please see:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.