Exemple #1
0
    def _rescheduleLrnCard(self,
                           card: Card,
                           conf: QueueConfig,
                           delay: int | None = None) -> Any:
        # normal delay for the current step?
        if delay is None:
            delay = self._delayForGrade(conf, card.left)

        card.due = int(time.time() + delay)
        # due today?
        if card.due < self.day_cutoff:
            # add some randomness, up to 5 minutes or 25%
            maxExtra = min(300, int(delay * 0.25))
            fuzz = random.randrange(0, max(1, maxExtra))
            card.due = min(self.day_cutoff - 1, card.due + fuzz)
            card.queue = QUEUE_TYPE_LRN
            if card.due < (int_time() + self.col.conf["collapseTime"]):
                self.lrnCount += 1
                # if the queue is not empty and there's nothing else to do, make
                # sure we don't put it at the head of the queue and end up showing
                # it twice in a row
                if self._lrnQueue and not self.revCount and not self.newCount:
                    smallestDue = self._lrnQueue[0][0]
                    card.due = max(card.due, smallestDue + 1)
                heappush(self._lrnQueue, (card.due, card.id))
        else:
            # the card is due in one or more days, so we need to use the
            # day learn queue
            ahead = ((card.due - self.day_cutoff) // 86400) + 1
            card.due = self.today + ahead
            card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
        return delay
Exemple #2
0
def test_collapse():
    col = getEmptyCol()
    # add a note
    note = col.newNote()
    note["Front"] = "one"
    col.addNote(note)
    # and another, so we don't get the same twice in a row
    note = col.newNote()
    note["Front"] = "two"
    col.addNote(note)
    col.reset()
    # first note
    c = col.sched.getCard()
    col.sched.answerCard(c, 1)
    # second note
    c2 = col.sched.getCard()
    assert c2.nid != c.nid
    col.sched.answerCard(c2, 1)
    # first should become available again, despite it being due in the future
    c3 = col.sched.getCard()
    assert c3.due > int_time()
    col.sched.answerCard(c3, 4)
    # answer other
    c4 = col.sched.getCard()
    col.sched.answerCard(c4, 4)
    assert not col.sched.getCard()
Exemple #3
0
    def render_all_latex(
        self,
        progress_cb: Callable[[int], bool] | None = None
    ) -> tuple[int, str] | None:
        """Render any LaTeX that is missing.

        If a progress callback is provided and it returns false, the operation
        will be aborted.

        If an error is encountered, returns (note_id, error_message)
        """
        last_progress = time.time()
        checked = 0
        for (nid, mid, flds) in self.col.db.execute(
                "select id, mid, flds from notes where flds like '%[%'"):

            model = self.col.models.get(mid)
            _html, errors = render_latex_returning_errors(flds,
                                                          model,
                                                          self.col,
                                                          expand_clozes=True)
            if errors:
                return (nid, "\n".join(errors))

            checked += 1
            elap = time.time() - last_progress
            if elap >= 0.3 and progress_cb is not None:
                last_progress = int_time()
                if not progress_cb(checked):
                    return None

        return None
Exemple #4
0
 def deck_due_tree(self, top_deck_id: DeckId | None = None) -> DeckTreeNode | None:
     """Returns a tree of decks with counts.
     If top_deck_id provided, only the according subtree is returned."""
     tree = self.col._backend.deck_tree(now=int_time())
     if top_deck_id:
         return self.col.decks.find_deck_in_tree(tree, top_deck_id)
     return tree
Exemple #5
0
def test_timing():
    col = getEmptyCol()
    # add a few review cards, due today
    for i in range(5):
        note = col.newNote()
        note["Front"] = f"num{str(i)}"
        col.addNote(note)
        c = note.cards()[0]
        c.type = CARD_TYPE_REV
        c.queue = QUEUE_TYPE_REV
        c.due = 0
        c.flush()
    # fail the first one
    col.reset()
    c = col.sched.getCard()
    col.sched.answerCard(c, 1)
    # the next card should be another review
    c2 = col.sched.getCard()
    assert c2.queue == QUEUE_TYPE_REV
    # if the failed card becomes due, it should show first
    c.due = int_time() - 1
    c.flush()
    col.reset()
    c = col.sched.getCard()
    assert c.queue == QUEUE_TYPE_LRN
Exemple #6
0
 def set_deck(self, cids: list[CardId], did: DeckId) -> None:
     self.col.set_deck(card_ids=cids, deck_id=did)
     self.col.db.execute(
         f"update cards set did=?,usn=?,mod=? where id in {ids2str(cids)}",
         did,
         self.col.usn(),
         int_time(),
     )
Exemple #7
0
    def seconds_since_last_sync(self) -> int:
        if self.is_syncing():
            return 0

        if self._log:
            last = self._log[-1].time
        else:
            last = 0
        return int_time() - last
Exemple #8
0
def test_filt_keep_lrn_state():
    col = getEmptyCol()

    note = col.newNote()
    note["Front"] = "one"
    col.addNote(note)

    # fail the card outside filtered deck
    c = col.sched.getCard()
    conf = col.sched._cardConf(c)
    conf["new"]["delays"] = [1, 10, 61]
    col.decks.save(conf)

    col.sched.answerCard(c, 1)

    assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
    assert c.left % 1000 == 3

    col.sched.answerCard(c, 3)
    assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN

    # create a dynamic deck and refresh it
    did = col.decks.new_filtered("Cram")
    col.sched.rebuild_filtered_deck(did)
    col.reset()

    # card should still be in learning state
    c.load()
    assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
    assert c.left % 1000 == 2

    # should be able to advance learning steps
    col.sched.answerCard(c, 3)
    # should be due at least an hour in the future
    assert c.due - int_time() > 60 * 60

    # emptying the deck preserves learning state
    col.sched.empty_filtered_deck(did)
    c.load()
    assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
    assert c.left % 1000 == 1
    assert c.due - int_time() > 60 * 60
Exemple #9
0
    def answerCard(self, card: Card, ease: int) -> None:
        if (not 1 <= ease <= 4) or (not 0 <= card.queue <= 4):
            raise Exception("invalid ease or queue")
        self.col.save_card_review_undo_info(card)
        if self._burySiblingsOnAnswer:
            self._burySiblings(card)

        self._answerCard(card, ease)

        card.mod = int_time()
        card.usn = self.col.usn()
        card.flush()
Exemple #10
0
    def _answerCardPreview(self, card: Card, ease: int) -> None:
        if not 1 <= ease <= 2:
            raise Exception("invalid ease")

        if ease == BUTTON_ONE:
            # repeat after delay
            card.queue = QUEUE_TYPE_PREVIEW
            card.due = int_time() + self._previewDelay(card)
            self.lrnCount += 1
        else:
            # BUTTON_TWO
            # restore original card state and remove from filtered deck
            self._restorePreviewCard(card)
            self._removeFromFiltered(card)
Exemple #11
0
 def updateData(self, n: ForeignNote, id: NoteId,
                sflds: list[str]) -> Optional[Updates]:
     self._ids.append(id)
     self.processFields(n, sflds)
     if self._tagsMapped:
         tags = self.col.tags.join(n.tags)
         return (
             int_time(),
             self.col.usn(),
             n.fieldsStr,
             tags,
             id,
             n.fieldsStr,
             tags,
         )
     elif self.tagModified:
         tags = self.col.db.scalar("select tags from notes where id = ?",
                                   id)
         tagList = self.col.tags.split(tags) + self.tagModified.split()
         tags = self.col.tags.join(tagList)
         return (int_time(), self.col.usn(), n.fieldsStr, tags, id,
                 n.fieldsStr)
     else:
         return (int_time(), self.col.usn(), n.fieldsStr, id, n.fieldsStr)
Exemple #12
0
 def _leftToday(
     self,
     delays: list[int],
     left: int,
     now: int | None = None,
 ) -> int:
     "The number of steps that can be completed by the day cutoff."
     if not now:
         now = int_time()
     delays = delays[-left:]
     ok = 0
     for idx, delay in enumerate(delays):
         now += int(delay * 60)
         if now > self.day_cutoff:
             break
         ok = idx
     return ok + 1
Exemple #13
0
    def _fillLrn(self) -> bool | list[Any]:
        if not self.lrnCount:
            return False
        if self._lrnQueue:
            return True
        cutoff = int_time() + self.col.conf["collapseTime"]
        self._lrnQueue = self.col.db.all(  # type: ignore
            f"""
select due, id from cards where
did in %s and queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_PREVIEW}) and due < ?
limit %d"""
            % (self._deck_limit(), self.reportLimit),
            cutoff,
        )
        self._lrnQueue = [cast(tuple[int, CardId], tuple(e)) for e in self._lrnQueue]
        # as it arrives sorted by did first, we need to sort it
        self._lrnQueue.sort()
        return self._lrnQueue
Exemple #14
0
 def _did(self, did: DeckId) -> Any:
     "Given did in src col, return local id."
     # already converted?
     if did in self._decks:
         return self._decks[did]
     # get the name in src
     g = self.src.decks.get(did)
     name = g["name"]
     # if there's a prefix, replace the top level deck
     if self.deckPrefix:
         tmpname = "::".join(DeckManager.path(name)[1:])
         name = self.deckPrefix
         if tmpname:
             name += f"::{tmpname}"
     # manually create any parents so we can pull in descriptions
     head = ""
     for parent in DeckManager.immediate_parent_path(name):
         if head:
             head += "::"
         head += parent
         idInSrc = self.src.decks.id(head)
         self._did(idInSrc)
     # if target is a filtered deck, we'll need a new deck name
     deck = self.dst.decks.by_name(name)
     if deck and deck["dyn"]:
         name = "%s %d" % (name, int_time())
     # create in local
     newid = self.dst.decks.id(name)
     # pull conf over
     if "conf" in g and g["conf"] != 1:
         conf = self.src.decks.get_config(g["conf"])
         self.dst.decks.save(conf)
         self.dst.decks.update_config(conf)
         g2 = self.dst.decks.get(newid)
         g2["conf"] = g["conf"]
         self.dst.decks.save(g2)
     # save desc
     deck = self.dst.decks.get(newid)
     deck["desc"] = g["desc"]
     self.dst.decks.save(deck)
     # add to deck map and return
     self._decks[did] = newid
     return newid
Exemple #15
0
def test_new():
    col = getEmptyCol()
    col.reset()
    assert col.sched.newCount == 0
    # add a note
    note = col.newNote()
    note["Front"] = "one"
    note["Back"] = "two"
    col.addNote(note)
    col.reset()
    assert col.sched.newCount == 1
    # fetch it
    c = col.sched.getCard()
    assert c
    assert c.queue == QUEUE_TYPE_NEW
    assert c.type == CARD_TYPE_NEW
    # if we answer it, it should become a learn card
    t = int_time()
    col.sched.answerCard(c, 1)
    assert c.queue == QUEUE_TYPE_LRN
    assert c.type == CARD_TYPE_LRN
    assert c.due >= t
Exemple #16
0
    def build_answer(self, *, card: Card, states: NextStates,
                     rating: CardAnswer.Rating.V) -> CardAnswer:
        "Build input for answer_card()."
        if rating == CardAnswer.AGAIN:
            new_state = states.again
        elif rating == CardAnswer.HARD:
            new_state = states.hard
        elif rating == CardAnswer.GOOD:
            new_state = states.good
        elif rating == CardAnswer.EASY:
            new_state = states.easy
        else:
            raise Exception("invalid rating")

        return CardAnswer(
            card_id=card.id,
            current_state=states.current,
            new_state=new_state,
            rating=rating,
            answered_at_millis=int_time(1000),
            milliseconds_taken=card.time_taken(capped=False),
        )
Exemple #17
0
 def newData(
     self, n: ForeignNote
 ) -> tuple[NoteId, str, NotetypeId, int, int, str, str, str, int, int,
            str]:
     id = self._nextID
     self._nextID = NoteId(self._nextID + 1)
     self._ids.append(id)
     self.processFields(n)
     # note id for card updates later
     for ord, c in list(n.cards.items()):
         self._cards.append((id, ord, c))
     return (
         id,
         guid64(),
         self.model["id"],
         int_time(),
         self.col.usn(),
         self.col.tags.join(n.tags),
         n.fieldsStr,
         "",
         0,
         0,
         "",
     )
Exemple #18
0
        else:
            return VideoDriver.Software

    @staticmethod
    def all_for_platform() -> list[VideoDriver]:
        all = [VideoDriver.OpenGL]
        if is_win:
            all.append(VideoDriver.ANGLE)
        all.append(VideoDriver.Software)
        return all


metaConf = dict(
    ver=0,
    updates=True,
    created=int_time(),
    id=random.randrange(0, 2**63),
    lastMsg=-1,
    suppressUpdate=False,
    firstRun=True,
    defaultLang=None,
)

profileConf: dict[str, Any] = dict(
    # profile
    mainWindowGeom=None,
    mainWindowState=None,
    numBackups=50,
    lastOptimize=int_time(),
    # editing
    searchHistory=[],
Exemple #19
0
def test_clock():
    col = getEmptyCol()
    if (col.sched.day_cutoff - int_time()) < 10 * 60:
        raise Exception("Unit tests will fail around the day rollover.")
Exemple #20
0
    def _importCards(self) -> None:
        if self.source_needs_upgrade:
            self.src.upgrade_to_v2_scheduler()
        # build map of (guid, ord) -> cid and used id cache
        self._cards: dict[tuple[str, int], CardId] = {}
        existing = {}
        for guid, ord, cid in self.dst.db.execute(
            "select f.guid, c.ord, c.id from cards c, notes f " "where c.nid = f.id"
        ):
            existing[cid] = True
            self._cards[(guid, ord)] = cid
        # loop through src
        cards = []
        revlog = []
        cnt = 0
        usn = self.dst.usn()
        aheadBy = self.src.sched.today - self.dst.sched.today
        for card in self.src.db.execute(
            "select f.guid, f.mid, c.* from cards c, notes f " "where c.nid = f.id"
        ):
            guid = card[0]
            if guid in self._ignoredGuids:
                continue
            # does the card's note exist in dst col?
            if guid not in self._notes:
                continue
            # does the card already exist in the dst col?
            ord = card[5]
            if (guid, ord) in self._cards:
                # fixme: in future, could update if newer mod time
                continue
            # doesn't exist. strip off note info, and save src id for later
            card = list(card[2:])
            scid = card[0]
            # ensure the card id is unique
            while card[0] in existing:
                card[0] += 999
            existing[card[0]] = True
            # update cid, nid, etc
            card[1] = self._notes[guid][0]
            card[2] = self._did(card[2])
            card[4] = int_time()
            card[5] = usn
            # review cards have a due date relative to collection
            if (
                card[7] in (QUEUE_TYPE_REV, QUEUE_TYPE_DAY_LEARN_RELEARN)
                or card[6] == CARD_TYPE_REV
            ):
                card[8] -= aheadBy
            # odue needs updating too
            if card[14]:
                card[14] -= aheadBy
            # if odid true, convert card from filtered to normal
            if card[15]:
                # odid
                card[15] = 0
                # odue
                card[8] = card[14]
                card[14] = 0
                # queue
                if card[6] == CARD_TYPE_LRN:  # type
                    card[7] = QUEUE_TYPE_NEW
                else:
                    card[7] = card[6]
                # type
                if card[6] == CARD_TYPE_LRN:
                    card[6] = CARD_TYPE_NEW
            cards.append(card)
            # we need to import revlog, rewriting card ids and bumping usn
            for rev in self.src.db.execute("select * from revlog where cid = ?", scid):
                rev = list(rev)
                rev[1] = card[0]
                rev[2] = self.dst.usn()
                revlog.append(rev)
            cnt += 1
        # apply
        self.dst.db.executemany(
            """
insert or ignore into cards values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
            cards,
        )
        self.dst.db.executemany(
            """
insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
            revlog,
        )
Exemple #21
0
 def deck_due_tree(self, top_deck_id: int = 0) -> DeckTreeNode:
     """Returns a tree of decks with counts.
     If top_deck_id provided, counts are limited to that node."""
     return self.col._backend.deck_tree(top_deck_id=top_deck_id,
                                        now=int_time())
Exemple #22
0
 def _log_and_notify(self, entry: LogEntry) -> None:
     entry_with_time = LogEntryWithTime(time=int_time(), entry=entry)
     self._log.append(entry_with_time)
     self.mw.taskman.run_on_main(
         lambda: gui_hooks.media_sync_did_progress(entry_with_time)
     )
Exemple #23
0
 def _updateLrnCutoff(self, force: bool) -> bool:
     nextCutoff = int_time() + self.col.conf["collapseTime"]
     if nextCutoff - self._lrnCutoff > 60 or force:
         self._lrnCutoff = nextCutoff
         return True
     return False