1#
2# cramming.py <Peter.Bienstman@UGent.be>
3#
4
5from mnemosyne.libmnemosyne.schedulers.SM2_mnemosyne import SM2Mnemosyne
6
7RANDOM = 0
8EARLIEST_FIRST = 1
9LATEST_FIRST = 2
10MOST_LAPSES_FIRST = 3
11
12
13class Cramming(SM2Mnemosyne):
14
15    UNSEEN = 0
16    WRONG = 1
17    RIGHT = 2
18
19    name = "cramming"
20
21    def __init__(self, component_manager):
22        SM2Mnemosyne.__init__(self, component_manager)
23        self.first_run = True
24
25    def reset(self, new_only=False):
26        if self.first_run and self.config()["cramming_store_state"] == False:
27            self.database().set_scheduler_data(self.UNSEEN)
28        self.first_run = False
29        SM2Mnemosyne.reset(self, new_only)
30
31    def rebuild_queue(self, learn_ahead=False):
32        db = self.database()
33        if not db.is_loaded() or not db.active_count():
34            return
35        max_ret_reps = 1 if self.new_only else -1 # TODO: make configurable
36        if self.new_only and db.recently_memorised_count(max_ret_reps) == 0:
37            return
38        self._card_ids_in_queue = []
39        self._fact_ids_in_queue = []
40        self.criterion = db.current_criterion()
41        # Determine sort key.
42        if self.config()["cramming_order"] == RANDOM:
43            sort_key = "random"
44        elif self.config()["cramming_order"] == EARLIEST_FIRST:
45            sort_key = "next_rep"
46        elif self.config()["cramming_order"] == LATEST_FIRST:
47            sort_key = "next_rep desc"
48        elif self.config()["cramming_order"] == MOST_LAPSES_FIRST:
49            sort_key = "lapses desc"
50        # Stage 1: do all the unseen cards.
51        if self.stage == 1:
52            for _card_id, _fact_id in db.cards_with_scheduler_data(self.UNSEEN,
53                    sort_key=sort_key, limit=25, max_ret_reps=max_ret_reps):
54                if _fact_id not in self._fact_ids_in_queue:
55                    self._card_ids_in_queue.append(_card_id)
56                    self._fact_ids_in_queue.append(_fact_id)
57            if len(self._card_ids_in_queue):
58                return
59            self.stage = 2
60        # Stage 2: do the cards we got wrong.
61        if self.stage == 2:
62            for _card_id, _fact_id in db.cards_with_scheduler_data(self.WRONG,
63                    sort_key=sort_key, limit=25, max_ret_reps=max_ret_reps):
64                if _fact_id not in self._fact_ids_in_queue:
65                    self._card_ids_in_queue.append(_card_id)
66                    self._fact_ids_in_queue.append(_fact_id)
67            if len(self._card_ids_in_queue):
68                return
69        # Start again.
70        self.database().set_scheduler_data(self.UNSEEN)
71        self.stage = 1
72        self.rebuild_queue()
73
74    def grade_answer(self, card, new_grade, dry_run=False):
75        # The dry run mode is typically used to determine the intervals
76        # for the different grades, so we don't want any side effects
77        # from hooks running then.
78        if not dry_run:
79            for f in self.component_manager.all("hook", "before_repetition"):
80                f.run(card)
81        # Do the actual grading.
82        if new_grade <= 1:
83            card.scheduler_data = self.WRONG
84        else:
85            card.scheduler_data = self.RIGHT
86        # Run hooks.
87        self.criterion.apply_to_card(card)
88        for f in self.component_manager.all("hook", "after_repetition"):
89            f.run(card)
90        return 0