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

Fix KeyError when accessing deck configuration in Anki 24+ #36

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
233 changes: 139 additions & 94 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
# License: GNU GPL v3 <www.gnu.org/licenses/gpl.html>

from __future__ import division
import datetime, time, math
from anki.hooks import wrap, addHook

import datetime
import math
import time

from anki.hooks import addHook, wrap
from anki.utils import intTime
from aqt import *
from aqt.main import AnkiQt
from anki.utils import intTime
from aqt.utils import showWarning, openHelp, getOnlyText, askUser, showInfo, openLink
from aqt.utils import (askUser, getOnlyText, openHelp, openLink, showInfo,
showWarning)

from .config import DeadlineDialog

deadlines = mw.addonManager.getConfig(__name__)
Expand All @@ -24,46 +30,51 @@
mw.form.menuTools.addMenu(DeadlineMenu)


# count new cards in a deck
# Count new cards in a deck
def new_cards_in_deck(deck_id):
new_cards = mw.col.db.scalar("""select
count()
from cards where
type = 0 and
queue != -1 and
did = ?""", deck_id)
return new_cards
return mw.col.db.scalar(
"""
SELECT count() FROM cards
WHERE type = 0
AND queue != -1
AND did = ?
AND queue != -2""",
deck_id,
) # Exclude suspended cards


# Find settings group ID
def find_settings_group_id(name):
dconf = mw.col.decks.all_config()
for k in dconf:
if k['name'] == name:
return k['id']
if k["name"] == name:
return k["id"]
# All I want is the group ID
return False


# Find decks in settings group
def find_decks_in_settings_group(group_id):
members = []
decks = mw.col.decks.decks
for d in decks:
if 'conf' in decks[d] and int(decks[d]['conf']) == int(group_id):
members.append(d)
return members
# Find decks using a specific config
def find_decks_in_settings_group(config_id):
decks = []
for deck in mw.col.decks.all():
if deck.get("conf_id") == config_id:
decks.append(deck["id"])
return decks


# Count new cards in settings group
def new_cards_in_settings_group(name):
new_cards = 0
new_today = 0
group_id = find_settings_group_id(name)
if group_id:
# Find decks and cycle through
# decks = find_decks_in_settings_group(group_id)
decks= mw.col.decks.didsForConf({"id":group_id})

# Get config id for the deck name
deck_id = mw.col.decks.id_for_name(name)
deck = mw.col.decks.get(deck_id)
config_id = deck.get("conf_id")

if config_id:
# Find all decks using this config
decks = find_decks_in_settings_group(config_id)
for d in decks:
new_cards += new_cards_in_deck(d)
new_today += first_seen_cards_in_deck(d)
Expand All @@ -72,24 +83,24 @@ def new_cards_in_settings_group(name):

# Count cards first seen today
def first_seen_cards_in_deck(deck_id):
#return mw.col.decks.decks[deck_id]["newToday"][1] #unreliable
#a new Anki day starts at 04:00 AM (by default); not midnight
dayStartTime = datetime.datetime.fromtimestamp(mw.col.crt).time()
midnight = datetime.datetime.combine(datetime.date.today(), dayStartTime)
midNight = int(time.mktime(midnight.timetuple()) * 1000)
query = ("""select count() from
(select r.id as review, c.id as card, c.did as deck
from revlog as r, cards as c
where r.cid = c.id
and r.type = 0
order by c.id, r.id DESC)
where deck = %s
and review >= %s
group by card""" % (deck_id, midNight))
ret = mw.col.db.scalar(query)
if not ret:
ret = 0
return ret
day_cutoff = mw.col.sched.day_cutoff
return (
mw.col.db.scalar(
"""
SELECT count() FROM (
SELECT r.id as review, c.id as card, c.did as deck
FROM revlog r, cards c
WHERE r.cid = c.id
AND r.type = 0
AND r.id >= ?
AND deck = ?
GROUP BY c.id
)""",
day_cutoff * 1000,
deck_id,
)
or 0
)


# find days until deadline
Expand All @@ -116,92 +127,125 @@ def cards_per_day(new_cards, days_left):
per_day = int(new_cards / days_left)
else:
per_day = int(new_cards / days_left) + 1
#sanity check
# sanity check
if per_day < 0:
per_day = 0
per_day = 0
return per_day


# update new cards per day of a settings group
def update_new_cards_per_day(name, per_day):
group_id = find_settings_group_id(name)
if group_id:
# if we have a group id; check all of thr available confs
for dconf in mw.col.decks.all_config():
if (group_id==dconf.get('id')):
# if I found the config that I'm trying to update, updat ethe config
dconf["new"]["perDay"] = int(per_day)
# utils.showInfo("updating deadlines disabled")
mw.col.decks.save(dconf)
#mw.col.decks.flush()


# Calc new cards per day
"""Update deck-specific new cards and review limits"""
# Get deck ID from name
deck_id = mw.col.decks.id_for_name(name)
if not deck_id:
return

# Get the deck
deck = mw.col.decks.get(deck_id)
if not deck:
return

# Update both the new cards per day limit and review limit
deck["newLimit"] = int(per_day) # Set the deck-specific override
deck["reviewLimit"] = int(per_day * 10) # Set review limit

# Save deck changes
mw.col.decks.save(deck)

# Also update the config for consistency
config_id = deck.get("conf_id")
if config_id:
config = mw.col.decks.get_config(config_id)
if config:
config["new"]["perDay"] = int(per_day)
mw.col.decks.update_config(config)

# Ensure changes are saved
mw.col.save()
mw.reset()


def calc_new_cards_per_day(name, days_left, silent=True):
"""Calculate and update the number of new cards per day"""
new_cards, new_today = new_cards_in_settings_group(name)
per_day = cards_per_day((new_cards + new_today), days_left)
total_cards = new_cards + new_today

if days_left <= 0:
per_day = total_cards # Show all remaining cards if past deadline
else:
per_day = cards_per_day(total_cards, days_left)

# Update deck configuration
update_new_cards_per_day(name, per_day)

return (name, new_today, new_cards, days_left, per_day)


# Main Function
def allDeadlines(silent=True):
"""Process all deadlines and update deck configurations"""
deadlines = mw.addonManager.getConfig(__name__)
# In the future, line 152-160 can be removed. this is to change the way that I store the config without breaking peoples compatability
deadlines.pop("test",1)
if(not "deadlines" in deadlines):
temp={}
temp["deadlines"]={}
for profile,profile_deadlines in deadlines.items():
temp["deadlines"][profile]=profile_deadlines
deadlines=temp
deadlines.pop("test", None)

if "deadlines" not in deadlines:
temp = {"deadlines": {}}
for profile, profile_deadlines in deadlines.items():
temp["deadlines"][profile] = profile_deadlines
deadlines = temp
mw.addonManager.writeConfig(__name__, deadlines)

profile = str(aqt.mw.pm.name)
include_today = True # include today in the number of days left
tempLogString=""
include_today = True
tempLogString = ""

if profile in deadlines["deadlines"]:
for deck,date in deadlines["deadlines"].get(profile).items():
# new_cards, new_today = new_cards_in_settings_group(name)
for deck, date in deadlines["deadlines"].get(profile).items():
days_left = days_until_deadline(date, include_today)
# Change per_day amount if there's still time
# before the deadline
if days_left:
(name, new_today, new_cards, days_left, per_day)=calc_new_cards_per_day(deck, days_left, silent)
if(deadlines.get("oneOrMany","")=="Many"):
if days_left is not False:
(name, new_today, new_cards, days_left, per_day) = (
calc_new_cards_per_day(deck, days_left, silent)
)
if deadlines.get("oneOrMany", "") == "Many":
if not silent:
logString="%s\n\nNew cards seen today: %s\nNew cards remaining: %s\nDays left: %s\nNew cards per day: %s" % (name, new_today, new_cards, days_left, per_day)
logString = f"{name}\n\nNew cards seen today: {new_today}\nNew cards remaining: {new_cards}\nDays left: {days_left}\nNew cards per day: {per_day}"
utils.showInfo(logString)
else:
tempLogString+="%s\nNew cards seen today: %s\nNew cards remaining: %s\nDays left: %s\nNew cards per day: %s\n\n" % (name, new_today, new_cards, days_left, per_day)
if(deadlines.get("oneOrMany","One")=="One"):
if not silent:
summaryPopup(tempLogString)
aqt.mw.deckBrowser.refresh()
tempLogString += f"{name}\nNew cards seen today: {new_today}\nNew cards remaining: {new_cards}\nDays left: {days_left}\nNew cards per day: {per_day}\n\n"

#Manual Version
if deadlines.get("oneOrMany", "One") == "One" and not silent:
summaryPopup(tempLogString)

# Make sure all changes are saved and refresh UI
mw.col.save()
mw.reset()


# Manual Version
def manualDeadlines():
allDeadlines(False)


def summaryPopup(text):
parent = aqt.mw.app.activeWindow() or aqt.mw
popup=QDialog(parent)
popup.resize(500,500)
layout=QVBoxLayout()
scroll=QScrollArea()
textbox=QLabel(text)
popup = QDialog(parent)
popup.resize(500, 500)
layout = QVBoxLayout()
scroll = QScrollArea()
textbox = QLabel(text)
scroll.setWidget(textbox)
scroll.ensureWidgetVisible(textbox)
layout.addWidget(scroll)
okButton = QDialogButtonBox.StandardButton.Ok
buttonBox=QDialogButtonBox(okButton)
buttonBox = QDialogButtonBox(okButton)
buttonBox.button(okButton).clicked.connect(closeSummary)
layout.addWidget(buttonBox)
popup.setLayout(layout)
popup.show()


def closeSummary():
aqt.mw.app.activeWindow().close()


manualDeadlineAction = QAction("Process Deadlines", mw)
manualDeadlineAction.triggered.connect(manualDeadlines)
configAction = QAction("Configure Deadlines", mw)
Expand All @@ -210,4 +254,5 @@ def closeSummary():
DeadlineMenu.addAction(manualDeadlineAction)

# Add hook to adjust Deadlines on load profile
addHook("profileLoaded", allDeadlines)
addHook("profileLoaded", allDeadlines)

Loading