Skip to content

Commit

Permalink
refactor code, improve performance, fix bugs
Browse files Browse the repository at this point in the history
Ok, this commit change a lot of this program

First of all, I needed to refactor the code to support
suspending/unsuspending of cards that were not added today (ie. cards
that are not "added:1")

I also made some structural changes to leverage batch editing of
cards (this improves performance a BUNCH)

And, last but not least, I fixed some bugs when suspending/unsuspending cards.
  • Loading branch information
Cardosaum committed Apr 24, 2021
1 parent deddcfa commit d192894
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 71 deletions.
Binary file modified hide_new_cards_until_next_day.ankiaddon
Binary file not shown.
152 changes: 88 additions & 64 deletions hide_new_cards_until_next_day/__init__.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
#!/usr/bin/env python

# import the main window object (mw) from aqt
from aqt import mw
# import all of the Qt GUI library
from aqt.qt import *
# import hooks
from anki.hooks import addHook
from aqt.utils import getText, showInfo
from anki.lang import _
# import the "show info" tool from utils.py
from aqt.utils import showInfo, qconnect
from anki import Collection

from aqt.utils import qconnect
from aqt.qt import QAction
from aqt import gui_hooks
from datetime import datetime
from time import time_ns
import re
from re import compile
from pathlib import Path
import json
from anki.tags import TagManager
from json import (dumps, loads)


# Load config file
Expand All @@ -30,15 +19,18 @@ def handle_config() -> dict:
}
mw.addonManager.writeConfig(__name__, config)
config_path = Path(mw.addonManager._addonMetaPath(__name__)).parent.absolute().joinpath('config.json')
config_path.write_text(json.dumps(config))
config_path.write_text(dumps(config))
return config


# We're going to add a menu item below. First we want to create a function to
# be called when the menu item is activated.
MARKER_SEP = '_'
MARKER_TAG_BASE = 'zTagHidden' + MARKER_SEP
REGEX_TAG = re.compile(f'^{MARKER_TAG_BASE}')
MARKER_TAG_BASE = 'ø::hide_new_cards_until_next_day'
MARKER_TAG_HIDDEN = MARKER_TAG_BASE + '::hidden_at::'
MARKER_TAG_REDEEM = MARKER_TAG_BASE + '::redeem_at::'
REGEX_TAG = compile(f'^{MARKER_TAG_HIDDEN}' + r'\d{4}-\d{,2}-\d{,2}$')
REGEX_TAG_REDEEM = compile(f'^{MARKER_TAG_REDEEM}' + r'\d{4}-\d{,2}-\d{,2}$')


def marker_today():
Expand All @@ -47,69 +39,98 @@ def marker_today():
return d


def marker_yesterday():
d = datetime.fromtimestamp(time_ns()//10**9)
d = datetime(d.year, d.month, d.day-1, 0)
return d


def marker_n(n: int):
d = datetime.fromtimestamp(time_ns()//10**9)
d = datetime(d.year, d.month, d.day + n, 0)
return d


def marker_tag() -> str:
return f'{MARKER_TAG_BASE}{marker_today().strftime("%Y-%m-%d")}'
return f'{MARKER_TAG_HIDDEN}{marker_today().strftime("%Y-%m-%d")}'


def suspend_cards(*args, **kargs) -> None:
added_today_only = kargs.get('added_today_only', False)
if added_today_only:
desired_cids = mw.col.find_cards("is:new -is:suspended added:1")
else:
desired_cids = mw.col.find_cards("is:new -is:suspended")
if desired_cids:
mw.col.sched.suspendCards(desired_cids)
desired_nids = get_ids_to_suspend(added_today_only, 'notes')
if desired_nids:
mw.col.tags.bulk_update(desired_nids, ' '.join(get_tags_to_remove()), '', False)
mw.col.tags.bulk_add(desired_nids, marker_tag())

# get rid of marker tags for all those other cards that doesn't need
needed = set(get_ids_to_suspend(added_today_only, 'notes'))
unneeded = set(mw.col.find_notes(f'tag:"{MARKER_TAG_BASE}*"'))
diff = list(unneeded - needed)
mw.col.tags.bulk_update(diff, ' '.join([x for x in mw.col.tags.all() if REGEX_TAG.search(x)]), '', False)
mw.reset()
def marker_tag_yesterday() -> str:
return f'{MARKER_TAG_BASE}{marker_yesterday().strftime("%Y-%m-%d")}'


def marker_tag_n(n: int) -> str:
return f'{MARKER_TAG_REDEEM}{marker_n(n).strftime("%Y-%m-%d")}'

def unsuspend_cards(*args, **kargs) -> None:
# unsuspend all the cards with the custom marker
# we don't need to worry about which exactly need
# to be marked/unmarked because we handle this in
# the suspend_cards function
desired_cids = mw.col.find_cards(f'tag:"{MARKER_TAG_BASE}*"')
if desired_cids:
mw.col.sched.unsuspendCards(desired_cids)

# remove all the marker tags
def suspend_cards_v2(*args, **kargs) -> None:
added_today_only = kargs.get('added_today_only', False)
desired_nids = get_ids_to_suspend(added_today_only, 'notes')
if desired_nids:
mw.col.tags.bulk_update(desired_nids, ' '.join(get_tags_to_remove()), '', False)
search = f'is:new -is:buried -is:suspended -tag:"{MARKER_TAG_REDEEM}*"'
if added_today_only:
search += ' added:1'

cids = mw.col.find_cards(search)
nids = mw.col.find_notes(search)

# suspend all cards that are new
# (either only from today or all new cards)
if cids:
mw.col.sched.suspendCards(cids)

# get the note id of this cards
# and add a new marker tag from today
# and a redeem tag for tomorrow
if nids:
mw.col.tags.bulk_add(nids, ' '.join([marker_tag(), marker_tag_n(1)]))

# remove unneded tags
marker_tags = [x for x in mw.col.tags.all() if REGEX_TAG.search(x) and x != marker_tag()]
if marker_tags:
tags_search = ' or '.join([f'tag:"{x}"' for x in marker_tags])
nids_tags_to_remove = mw.col.find_notes(tags_search)
if nids_tags_to_remove:
mw.col.tags.bulk_update(nids_tags_to_remove, ' '.join(marker_tags), '', False)
mw.reset()


def get_tags_to_remove() -> list:
all_tags = mw.col.tags.all()
return [x for x in all_tags if REGEX_TAG.search(x) and x != marker_tag()]
def is_redeem_tag_expired(tag: str) -> bool:
tag = tag.strip().removeprefix(MARKER_TAG_REDEEM)
d = datetime.strptime(tag, '%Y-%m-%d')
if (datetime.now() - d).days >= 0:
return True
return False


def get_ids_to_suspend(added_today_only: bool, note_or_card: str) -> list:
def unsuspend_cards_v2(*args, **kargs) -> None:
# unsuspend all cards whose redeem date has expired
added_today_only = kargs.get('added_today_only', False)
if added_today_only:
d = datetime.fromtimestamp(time_ns()//10**9)
d = int(int(datetime(d.year, d.month, d.day, 0).timestamp()) * 10**3)
ids = mw.col.db.all(f"select {note_or_card}.id from cards join notes on cards.nid = notes.id where (cards.type = 0 or cards.queue = 0) and notes.id >= {d}")
redeem_tags = [x for x in mw.col.tags.all() if REGEX_TAG_REDEEM.search(x)]
else:
ids = mw.col.db.all(f"select {note_or_card}.id from cards join notes on cards.nid = notes.id where (cards.type = 0 or cards.queue = 0)")
redeem_tags = [x for x in mw.col.tags.all() if REGEX_TAG_REDEEM.search(x) and is_redeem_tag_expired(x)]

if ids:
ids = [int(x[0]) for x in ids]
return ids
if redeem_tags:
tags_search = ' or '.join([f'tag:"{x}"' for x in redeem_tags])
else:
return

if added_today_only:
tags_search = f'({tags_search}) -added:1'

cids = mw.col.find_cards(tags_search)
if cids:
mw.col.sched.unsuspendCards(cids)

# remove unneeded mark tags
nids = mw.col.find_notes(tags_search)
if nids:
mw.col.tags.bulk_update(nids, f'{marker_tag()} {" ".join(redeem_tags)} {" ".join([x for x in mw.col.tags.all() if REGEX_TAG.search(x)])}', '', False)
mw.reset()


def marker_main(*args, **kargs) -> None:
try:
config_added_today_only = json.loads(args[0]).get('added_today_only', 'somethin_else')
config_added_today_only = loads(args[0]).get('added_today_only', 'somethin_else')
except Exception:
config_added_today_only = handle_config().get('added_today_only', 'something_else')

Expand All @@ -118,8 +139,8 @@ def marker_main(*args, **kargs) -> None:
elif config_added_today_only not in [True, False]:
raise ValueError(f'the key "added_today_only" must be either "true" or "false", but found {config_added_today_only}')

unsuspend_cards(added_today_only=config_added_today_only)
suspend_cards(added_today_only=config_added_today_only)
suspend_cards_v2(added_today_only=config_added_today_only)
unsuspend_cards_v2(added_today_only=config_added_today_only)
if args:
return args[0]

Expand All @@ -128,9 +149,12 @@ def marker_main(*args, **kargs) -> None:
gui_hooks.add_cards_did_add_note.append(marker_main)
gui_hooks.main_window_did_init.append(marker_main)
gui_hooks.addon_config_editor_will_save_json.append(marker_main)

# create a new menu item, "Organizar Cartões"
action = QAction("Hide new cards until next day", mw)

# set it to call testFunction when it's clicked
qconnect(action.triggered, marker_main)

# and add it to the tools menu
mw.form.menuTools.addAction(action)
16 changes: 9 additions & 7 deletions hide_new_cards_until_next_day/config.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# [Hide new cards until next day](https://ankiweb.net/shared/info/1999327581)
# [HIDE NEW CARDS UNTIL NEXT DAY](https://ankiweb.net/shared/info/1999327581)

Disclaimer: This is my first addon and is by no means free of bugs, please keep in mind that undesired behavior can happen. I'd suggest to backup your collection frequently.

## Addon Options
## ADDON OPTIONS

Currently this addon supports two possible hide options:

Expand All @@ -14,17 +14,19 @@ You can configure the default behavior modifying the option `"added_today_only"`
If this option is `"false"` (the default), then the first behavior listed will
take place. If `"true"`, then the second will.

## How this addon words?
## HOW THIS ADDON WORDS?

It's function is basically the following:<br/>
First we find all new cards (issuing a query like: "is:new -is:suspended"),<br/>
Then we hide all the found cards (respecting the option `added_today_only` mentioned above).<br/>
We need to mark somehow which cards were suspended by this extension, so we are able to unsuspend them.<br/>
In order to achieve this, we add a tag in this format to each suspended card: "`zTagHidden_<year>-<month>-<day>`".<br/>
Due to this usage of tags you may experience an accumulation of "`zTagHidden_X`" tags. You only need to click in 'Tools > Check Database'<br/>
to get rid of the unused ones.
In order to achieve this, we add a tag in this format to each suspended card: "`ø::hide_new_cards_until_next_day::[hidden_at,redeem_at]::<year>-<month>-<day>`".<br/>
Due to this usage of tags you may experience an accumulation of "`ø::hide_new_cards_until_next_day::X`" tags. You only need to click in 'Tools > Check Database'<br/>
to get rid of the unused ones.<br/>
<br/>
BTW, just as a side note: if you are curious about why I chose to use such a 'excentric' tag format, consider that the character `ø` is one of the last chareters in the sort order, so the tags created by this addon will show up only at the bottom of the browser. And, if you didn't already get the `::` thing, just look at [this addon](https://ankiweb.net/shared/info/594329229) ;)<br/>

## When this addon is executed?
## WHEN THIS ADDON IS EXECUTED?

This addon has 3 triggers and 1 menu button.

Expand Down

0 comments on commit d192894

Please sign in to comment.