mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 22:42:25 -04:00
Revert "obsolote relativeDelay in favour of interval desc, should fix ubuntu too"
This reverts commit 4558a36d3a
.
This commit is contained in:
parent
4558a36d3a
commit
2175fa2343
4 changed files with 66 additions and 63 deletions
|
@ -60,6 +60,7 @@ cardsTable = Table(
|
||||||
Column('noCount', Integer, nullable=False, default=0),
|
Column('noCount', Integer, nullable=False, default=0),
|
||||||
# cache
|
# cache
|
||||||
Column('spaceUntil', Float, nullable=False, default=0),
|
Column('spaceUntil', Float, nullable=False, default=0),
|
||||||
|
Column('relativeDelay', Float, nullable=False, default=0),
|
||||||
Column('isDue', Boolean, nullable=False, default=0),
|
Column('isDue', Boolean, nullable=False, default=0),
|
||||||
Column('type', Integer, nullable=False, default=2),
|
Column('type', Integer, nullable=False, default=2),
|
||||||
Column('combinedDue', Integer, nullable=False, default=0))
|
Column('combinedDue', Integer, nullable=False, default=0))
|
||||||
|
@ -146,14 +147,8 @@ class Card(object):
|
||||||
return findTag(tag, alltags)
|
return findTag(tag, alltags)
|
||||||
|
|
||||||
def fromDB(self, s, id):
|
def fromDB(self, s, id):
|
||||||
r = s.first("""select
|
r = s.first("select * from cards where id = :id",
|
||||||
id, factId, cardModelId, created, modified, tags, ordinal, question, answer,
|
id=id)
|
||||||
priority, interval, lastInterval, due, lastDue, factor,
|
|
||||||
lastFactor, firstAnswered, reps, successive, averageTime, reviewTime,
|
|
||||||
youngEase0, youngEase1, youngEase2, youngEase3, youngEase4,
|
|
||||||
matureEase0, matureEase1, matureEase2, matureEase3, matureEase4,
|
|
||||||
yesCount, noCount, spaceUntil, isDue, type, combinedDue
|
|
||||||
from cards where id = :id""", id=id)
|
|
||||||
if not r:
|
if not r:
|
||||||
return
|
return
|
||||||
(self.id,
|
(self.id,
|
||||||
|
@ -190,6 +185,7 @@ from cards where id = :id""", id=id)
|
||||||
self.yesCount,
|
self.yesCount,
|
||||||
self.noCount,
|
self.noCount,
|
||||||
self.spaceUntil,
|
self.spaceUntil,
|
||||||
|
self.relativeDelay,
|
||||||
self.isDue,
|
self.isDue,
|
||||||
self.type,
|
self.type,
|
||||||
self.combinedDue) = r
|
self.combinedDue) = r
|
||||||
|
@ -230,6 +226,7 @@ matureEase4=:matureEase4,
|
||||||
yesCount=:yesCount,
|
yesCount=:yesCount,
|
||||||
noCount=:noCount,
|
noCount=:noCount,
|
||||||
spaceUntil = :spaceUntil,
|
spaceUntil = :spaceUntil,
|
||||||
|
relativeDelay = :interval / (strftime("%s", "now") - :due + 1),
|
||||||
isDue = :isDue,
|
isDue = :isDue,
|
||||||
type = :type,
|
type = :type,
|
||||||
combinedDue = max(:spaceUntil, :due)
|
combinedDue = max(:spaceUntil, :due)
|
||||||
|
|
103
anki/deck.py
103
anki/deck.py
|
@ -29,7 +29,8 @@ PRIORITY_NORM = 2
|
||||||
PRIORITY_LOW = 1
|
PRIORITY_LOW = 1
|
||||||
PRIORITY_NONE = 0
|
PRIORITY_NONE = 0
|
||||||
MATURE_THRESHOLD = 21
|
MATURE_THRESHOLD = 21
|
||||||
NEW_INTERVAL = 0
|
# need interval > 0 to ensure relative delay is ordered properly
|
||||||
|
NEW_INTERVAL = 0.001
|
||||||
NEW_CARDS_LAST = 1
|
NEW_CARDS_LAST = 1
|
||||||
NEW_CARDS_DISTRIBUTE = 0
|
NEW_CARDS_DISTRIBUTE = 0
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ decksTable = Table(
|
||||||
Column('created', Float, nullable=False, default=time.time),
|
Column('created', Float, nullable=False, default=time.time),
|
||||||
Column('modified', Float, nullable=False, default=time.time),
|
Column('modified', Float, nullable=False, default=time.time),
|
||||||
Column('description', UnicodeText, nullable=False, default=u""),
|
Column('description', UnicodeText, nullable=False, default=u""),
|
||||||
Column('version', Integer, nullable=False, default=12),
|
Column('version', Integer, nullable=False, default=11),
|
||||||
Column('currentModelId', Integer, ForeignKey("models.id")),
|
Column('currentModelId', Integer, ForeignKey("models.id")),
|
||||||
# syncing
|
# syncing
|
||||||
Column('syncName', UnicodeText),
|
Column('syncName', UnicodeText),
|
||||||
|
@ -107,7 +108,6 @@ class Deck(object):
|
||||||
|
|
||||||
def getCard(self, orm=True):
|
def getCard(self, orm=True):
|
||||||
"Return the next card object, or None."
|
"Return the next card object, or None."
|
||||||
self.checkDue()
|
|
||||||
id = self.getCardId()
|
id = self.getCardId()
|
||||||
if id:
|
if id:
|
||||||
return self.cardFromId(id, orm)
|
return self.cardFromId(id, orm)
|
||||||
|
@ -115,7 +115,6 @@ class Deck(object):
|
||||||
def getCards(self, limit=1, orm=True):
|
def getCards(self, limit=1, orm=True):
|
||||||
"""Return LIMIT number of new card objects.
|
"""Return LIMIT number of new card objects.
|
||||||
Caller must ensure multiple cards of the same fact are not shown."""
|
Caller must ensure multiple cards of the same fact are not shown."""
|
||||||
self.checkDue()
|
|
||||||
ids = self.getCardIds(limit)
|
ids = self.getCardIds(limit)
|
||||||
return [self.cardFromId(x, orm) for x in ids]
|
return [self.cardFromId(x, orm) for x in ids]
|
||||||
|
|
||||||
|
@ -268,6 +267,8 @@ where id != :id and factId = :factId""",
|
||||||
entry = CardHistoryEntry(card, ease, lastDelay)
|
entry = CardHistoryEntry(card, ease, lastDelay)
|
||||||
entry.writeSQL(self.s)
|
entry.writeSQL(self.s)
|
||||||
self.modified = now
|
self.modified = now
|
||||||
|
# update isDue for failed cards
|
||||||
|
self.markExpiredCardsDue()
|
||||||
# invalidate counts
|
# invalidate counts
|
||||||
self._countsDirty = True
|
self._countsDirty = True
|
||||||
|
|
||||||
|
@ -286,16 +287,24 @@ then 1 -- review
|
||||||
else 2 -- new
|
else 2 -- new
|
||||||
end)""" + where)
|
end)""" + where)
|
||||||
|
|
||||||
def checkDue(self):
|
def markExpiredCardsDue(self):
|
||||||
"Mark expired cards due."
|
"Mark expired cards due, and update their relativeDelay."
|
||||||
self.s.statement("""update cards
|
self.s.statement("""update cards
|
||||||
set isDue = 1
|
set isDue = 1, relativeDelay = interval / (strftime("%s", "now") - due + 1)
|
||||||
where isDue = 0 and priority in (1,2,3,4) and type in (0, 1, 2)
|
where isDue = 0 and priority in (1,2,3,4) and combinedDue < :now""",
|
||||||
and combinedDue < :now""",
|
|
||||||
now=time.time())
|
now=time.time())
|
||||||
|
|
||||||
def rebuildQueue(self):
|
def updateRelativeDelays(self):
|
||||||
|
"Update relative delays for expired cards."
|
||||||
|
self.s.statement("""update cards
|
||||||
|
set relativeDelay = interval / (strftime("%s", "now") - due + 1)
|
||||||
|
where isDue = 1 and type = 1""")
|
||||||
|
|
||||||
|
def rebuildQueue(self, updateRelative=True):
|
||||||
"Update relative delays based on current time."
|
"Update relative delays based on current time."
|
||||||
|
if updateRelative:
|
||||||
|
self.updateRelativeDelays()
|
||||||
|
self.markExpiredCardsDue()
|
||||||
# cache global/daily stats
|
# cache global/daily stats
|
||||||
self._globalStats = globalStats(self)
|
self._globalStats = globalStats(self)
|
||||||
self._dailyStats = dailyStats(self)
|
self._dailyStats = dailyStats(self)
|
||||||
|
@ -406,7 +415,7 @@ factor = 2.5, reps = 0, successive = 0, averageTime = 0, reviewTime = 0,
|
||||||
youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0,
|
youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0,
|
||||||
youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0,
|
youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0,
|
||||||
matureEase3 = 0,matureEase4 = 0, yesCount = 0, noCount = 0,
|
matureEase3 = 0,matureEase4 = 0, yesCount = 0, noCount = 0,
|
||||||
spaceUntil = 0, isDue = 0, type = 2,
|
spaceUntil = 0, relativeDelay = 0, isDue = 0, type = 2,
|
||||||
combinedDue = created, modified = :now, due = created
|
combinedDue = created, modified = :now, due = created
|
||||||
where id in %s""" % ids2str(ids), now=time.time(), new=NEW_INTERVAL)
|
where id in %s""" % ids2str(ids), now=time.time(), new=NEW_INTERVAL)
|
||||||
self.flushMod()
|
self.flushMod()
|
||||||
|
@ -576,15 +585,15 @@ suspended</a> cards.''') % {
|
||||||
|
|
||||||
def suspendedCardCount(self):
|
def suspendedCardCount(self):
|
||||||
return self.s.scalar(
|
return self.s.scalar(
|
||||||
"select count(id) from cards where type in (0,1,2) and priority = 0")
|
"select count(id) from cards where priority = 0")
|
||||||
|
|
||||||
def seenCardCount(self):
|
def seenCardCount(self):
|
||||||
return self.s.scalar(
|
return self.s.scalar(
|
||||||
"select count(id) from cards where type in (0, 1)")
|
"select count(id) from cards where reps != 0")
|
||||||
|
|
||||||
def newCardCount(self):
|
def newCardCount(self):
|
||||||
return self.s.scalar(
|
return self.s.scalar(
|
||||||
"select count(id) from cards where type = 2")
|
"select count(id) from cards where reps = 0")
|
||||||
|
|
||||||
# Counts related to due cards
|
# Counts related to due cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -641,8 +650,7 @@ select count(id) from failedCardsNow""")
|
||||||
def spacedCardCount(self):
|
def spacedCardCount(self):
|
||||||
return self.s.scalar("""
|
return self.s.scalar("""
|
||||||
select count(cards.id) from cards where
|
select count(cards.id) from cards where
|
||||||
isDue = 0 and type in (0,1,2) and
|
priority != 0 and due < :now and spaceUntil > :now""",
|
||||||
priority in (1,2,3,4) and due < :now and spaceUntil > :now""",
|
|
||||||
now=time.time())
|
now=time.time())
|
||||||
|
|
||||||
def isEmpty(self):
|
def isEmpty(self):
|
||||||
|
@ -1599,17 +1607,23 @@ alter table decks add column utcOffset numeric(10, 2) not null default 0""")
|
||||||
"Add indices to the DB."
|
"Add indices to the DB."
|
||||||
# card queues
|
# card queues
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
create index if not exists ix_cards_combinedDue on cards
|
create index if not exists ix_cards_markExpired on cards
|
||||||
(type, isDue, combinedDue, priority)""")
|
(isDue, priority desc, combinedDue desc)""")
|
||||||
|
deck.s.statement("""
|
||||||
|
create index if not exists ix_cards_failedIsDue on cards
|
||||||
|
(type, isDue, combinedDue)""")
|
||||||
|
deck.s.statement("""
|
||||||
|
create index if not exists ix_cards_failedOrder on cards
|
||||||
|
(type, isDue, due)""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
create index if not exists ix_cards_revisionOrder on cards
|
create index if not exists ix_cards_revisionOrder on cards
|
||||||
(type, isDue, priority desc, interval desc)""")
|
(type, isDue, priority desc, relativeDelay)""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
create index if not exists ix_cards_newRandomOrder on cards
|
create index if not exists ix_cards_newRandomOrder on cards
|
||||||
(type, isDue, priority desc, factId, ordinal)""")
|
(priority desc, factId, ordinal)""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
create index if not exists ix_cards_newOrderedOrder on cards
|
create index if not exists ix_cards_newOrderedOrder on cards
|
||||||
(type, isDue, priority desc, combinedDue)""")
|
(priority desc, due)""")
|
||||||
# card spacing
|
# card spacing
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
create index if not exists ix_cards_factId on cards (factId)""")
|
create index if not exists ix_cards_factId on cards (factId)""")
|
||||||
|
@ -1640,47 +1654,48 @@ create index if not exists ix_mediaDeleted_factId on mediaDeleted (mediaId)""")
|
||||||
def _addViews(deck):
|
def _addViews(deck):
|
||||||
"Add latest version of SQL views to DB."
|
"Add latest version of SQL views to DB."
|
||||||
s = deck.s
|
s = deck.s
|
||||||
# old views
|
# old tables
|
||||||
s.statement("drop view if exists failedCards")
|
s.statement("drop view if exists failedCards")
|
||||||
s.statement("drop view if exists acqCards")
|
s.statement("drop view if exists acqCards")
|
||||||
s.statement("drop view if exists futureCards")
|
s.statement("drop view if exists futureCards")
|
||||||
s.statement("drop view if exists typedCards")
|
s.statement("drop view if exists typedCards")
|
||||||
s.statement("drop view if exists failedCards")
|
s.statement("drop view if exists failedCards")
|
||||||
s.statement("drop view if exists failedCardsNow")
|
s.statement("drop view if exists failedCardsNow")
|
||||||
s.statement("drop view if exists failedCardsSoon")
|
|
||||||
s.statement("drop view if exists revCards")
|
|
||||||
s.statement("drop view if exists acqCardsRandom")
|
|
||||||
s.statement("drop view if exists acqCardsOrdered")
|
|
||||||
s.statement("""
|
s.statement("""
|
||||||
create view failedCardsNow as
|
create view failedCardsNow as
|
||||||
select * from cards
|
select * from cards
|
||||||
where type = 0 and isDue = 1
|
where type = 0 and isDue = 1
|
||||||
and +priority in (1,2,3,4)
|
and combinedDue <= (strftime("%s", "now") + 1)
|
||||||
order by type, isDue, combinedDue
|
order by combinedDue
|
||||||
""")
|
""")
|
||||||
|
s.statement("drop view if exists failedCardsSoon")
|
||||||
s.statement("""
|
s.statement("""
|
||||||
create view failedCardsSoon as
|
create view failedCardsSoon as
|
||||||
select * from cards
|
select * from cards
|
||||||
where type = 0 and +priority in (1,2,3,4)
|
where type = 0 and priority != 0
|
||||||
and combinedDue <= (select max(delay0, delay1) +
|
and combinedDue <=
|
||||||
strftime("%s", "now")+1 from decks where id = 1)
|
(select max(delay0, delay1)+strftime("%s", "now")+1
|
||||||
order by type, isDue, combinedDue
|
from decks)
|
||||||
|
order by modified
|
||||||
""")
|
""")
|
||||||
|
s.statement("drop view if exists revCards")
|
||||||
s.statement("""
|
s.statement("""
|
||||||
create view revCards as
|
create view revCards as
|
||||||
select * from cards
|
select * from cards where
|
||||||
where type = 1 and isDue = 1
|
type = 1 and isDue = 1
|
||||||
order by priority desc, interval desc""")
|
order by type, isDue, priority desc, relativeDelay""")
|
||||||
|
s.statement("drop view if exists acqCardsRandom")
|
||||||
s.statement("""
|
s.statement("""
|
||||||
create view acqCardsRandom as
|
create view acqCardsRandom as
|
||||||
select * from cards
|
select * from cards
|
||||||
where type = 2 and isDue = 1
|
where type = 2 and isDue = 1
|
||||||
order by priority desc, factId, ordinal""")
|
order by priority desc, factId, ordinal""")
|
||||||
|
s.statement("drop view if exists acqCardsOrdered")
|
||||||
s.statement("""
|
s.statement("""
|
||||||
create view acqCardsOrdered as
|
create view acqCardsOrdered as
|
||||||
select * from cards
|
select * from cards
|
||||||
where type = 2 and isDue = 1
|
where type = 2 and isDue = 1
|
||||||
order by priority desc, combinedDue""")
|
order by priority desc, due""")
|
||||||
_addViews = staticmethod(_addViews)
|
_addViews = staticmethod(_addViews)
|
||||||
|
|
||||||
def _upgradeDeck(deck, path):
|
def _upgradeDeck(deck, path):
|
||||||
|
@ -1692,6 +1707,8 @@ order by priority desc, combinedDue""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
alter table cards add column spaceUntil float not null default 0""")
|
alter table cards add column spaceUntil float not null default 0""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
|
alter table cards add column relativeDelay float not null default 0.0""")
|
||||||
|
deck.s.statement("""
|
||||||
alter table cards add column isDue boolean not null default 0""")
|
alter table cards add column isDue boolean not null default 0""")
|
||||||
deck.s.statement("""
|
deck.s.statement("""
|
||||||
alter table cards add column type integer not null default 0""")
|
alter table cards add column type integer not null default 0""")
|
||||||
|
@ -1821,20 +1838,6 @@ alter table models add column source integer not null default 0""")
|
||||||
DeckStorage._setUTCOffset(deck)
|
DeckStorage._setUTCOffset(deck)
|
||||||
deck.version = 11
|
deck.version = 11
|
||||||
deck.s.commit()
|
deck.s.commit()
|
||||||
if deck.version < 12:
|
|
||||||
deck.s.statement("drop index if exists ix_cards_revisionOrder")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_newRandomOrder")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_newOrderedOrder")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_markExpired")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_failedIsDue")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_failedOrder")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_type")
|
|
||||||
deck.s.statement("drop index if exists ix_cards_priority")
|
|
||||||
DeckStorage._addViews(deck)
|
|
||||||
DeckStorage._addIndices(deck)
|
|
||||||
deck.s.statement("analyze")
|
|
||||||
deck.version = 12
|
|
||||||
deck.s.commit()
|
|
||||||
return deck
|
return deck
|
||||||
_upgradeDeck = staticmethod(_upgradeDeck)
|
_upgradeDeck = staticmethod(_upgradeDeck)
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ class AnkiExporter(Exporter):
|
||||||
delete from reviewHistory""")
|
delete from reviewHistory""")
|
||||||
self.newDeck.s.statement("""
|
self.newDeck.s.statement("""
|
||||||
update cards set
|
update cards set
|
||||||
interval = 0,
|
interval = 0.001,
|
||||||
lastInterval = 0,
|
lastInterval = 0,
|
||||||
due = created,
|
due = created,
|
||||||
lastDue = 0,
|
lastDue = 0,
|
||||||
|
@ -101,6 +101,7 @@ yesCount = 0,
|
||||||
noCount = 0,
|
noCount = 0,
|
||||||
spaceUntil = 0,
|
spaceUntil = 0,
|
||||||
isDue = 1,
|
isDue = 1,
|
||||||
|
relativeDelay = 0,
|
||||||
type = 2,
|
type = 2,
|
||||||
combinedDue = created,
|
combinedDue = created,
|
||||||
modified = :now
|
modified = :now
|
||||||
|
|
10
anki/sync.py
10
anki/sync.py
|
@ -516,7 +516,8 @@ priority, interval, lastInterval, due, lastDue, factor,
|
||||||
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
|
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
|
||||||
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
|
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
|
||||||
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
|
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
|
||||||
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue)
|
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue,
|
||||||
|
relativeDelay)
|
||||||
values
|
values
|
||||||
(:id, :factId, :cardModelId, :created, :modified, :tags, :ordinal,
|
(:id, :factId, :cardModelId, :created, :modified, :tags, :ordinal,
|
||||||
:priority, :interval, :lastInterval, :due, :lastDue, :factor,
|
:priority, :interval, :lastInterval, :due, :lastDue, :factor,
|
||||||
|
@ -524,7 +525,7 @@ values
|
||||||
:youngEase1, :youngEase2, :youngEase3, :youngEase4, :matureEase0,
|
:youngEase1, :youngEase2, :youngEase3, :youngEase4, :matureEase0,
|
||||||
:matureEase1, :matureEase2, :matureEase3, :matureEase4, :yesCount,
|
:matureEase1, :matureEase2, :matureEase3, :matureEase4, :yesCount,
|
||||||
:noCount, :question, :answer, :lastFactor, :spaceUntil, :isDue,
|
:noCount, :question, :answer, :lastFactor, :spaceUntil, :isDue,
|
||||||
:type, :combinedDue)""", dlist)
|
:type, :combinedDue, 0)""", dlist)
|
||||||
self.deck.s.statement(
|
self.deck.s.statement(
|
||||||
"delete from cardsDeleted where cardId in %s" %
|
"delete from cardsDeleted where cardId in %s" %
|
||||||
ids2str([c[0] for c in cards]))
|
ids2str([c[0] for c in cards]))
|
||||||
|
@ -789,10 +790,11 @@ priority, interval, lastInterval, due, lastDue, factor,
|
||||||
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
|
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
|
||||||
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
|
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
|
||||||
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
|
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
|
||||||
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue)
|
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue,
|
||||||
|
relativeDelay)
|
||||||
values
|
values
|
||||||
(:id, :factId, :cardModelId, :created, :t, "", :ordinal,
|
(:id, :factId, :cardModelId, :created, :t, "", :ordinal,
|
||||||
1, 0, 0, :created, 0, 2.5,
|
1, 0.001, 0, :created, 0, 2.5,
|
||||||
0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0,
|
||||||
|
|
Loading…
Reference in a new issue