some additional actions (#185)

* implement needed actions

* add documentation and tests for cardInfo, updateCompleteDeck, reloadCollection, cardReviews, getLatestReviewID, insertReviews

Co-authored-by: Julian Müller <julian.mueller@stud-mail.uni-wuerzburg.de>
This commit is contained in:
Julian Müller 2020-07-12 21:53:31 +02:00 committed by GitHub
parent 84586cb352
commit ce0cc7dce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 308 additions and 4 deletions

View File

@ -271,7 +271,14 @@
"css":"p {font-family:Arial;}",
"cardId": 1498938915662,
"interval": 16,
"note":1502298033753
"note":1502298033753,
"ord": 1,
"type": 0,
"queue": 0,
"due": 1,
"reps": 1,
"lapses": 0,
"left": 6
},
{
"answer": "back content",
@ -286,7 +293,14 @@
"css":"p {font-family:Arial;}",
"cardId": 1502098034048,
"interval": 23,
"note":1502298033753
"note":1502298033753,
"ord": 1,
"type": 0,
"queue": 0,
"due": 1,
"reps": 1,
"lapses": 0,
"left": 6
}
],
"error": null

View File

@ -329,3 +329,73 @@
"error": null
}
```
* **updateCompleteDeck**
Pastes all transmitted data into the database and reloads the collection.
You can send a deckName and corresponding cards, notes & models.
All cards are assumed to belong to the given deck.
All notes referenced by given cards should be present.
All models referenced by given notes should be present.
*Sample request*:
```json
{
"action": "updateCompleteDeck",
"version": 6,
"params": {
"data": {
"deck": "test3",
"cards": {
"1485369472028": {
"id": 1485369472028,
"nid": 1485369340204,
"ord": 0,
"type": 0,
"queue": 0,
"due": 1186031,
"factor": 0,
"ivl": 0,
"reps": 0,
"lapses": 0,
"left": 0
}
},
"notes": {
"1485369340204": {
"id": 1485369340204,
"mid": 1375786181313,
"fields": [
"frontValue",
"backValue"
],
"tags": [
"aTag"
]
}
},
"models": {
"1375786181313": {
"id": 1375786181313,
"name": "anotherModel",
"fields": [
"Front",
"Back"
],
"templateNames": [
"Card 1"
]
}
}
}
}
}
```
*Sample result*:
```json
{
"result": null,
"error": null
}
```

View File

@ -168,3 +168,23 @@
"error": null
}
```
* **reloadCollection**
Tells anki to reload all data from the database.
*Sample request*:
```json
{
"action": "reloadCollection",
"version": 6
}
```
*Sample result*:
```json
{
"result": null,
"error": null
}
```

View File

@ -42,3 +42,81 @@
"result": "<center> lots of HTML here </center>"
}
```
* **cardReviews**
Requests all card reviews for a specified deck after a certain time.
`startID` is the latest unix time not included in the result.
Returns a list of 9-tuples `(reviewTime, cardID, usn, buttonPressed, newInterval, previousInterval, newFactor, reviewDuration, reviewType)`
*Sample request*:
```json
{
"action": "cardReviews",
"version": 6,
"params": {
"deck": "default",
"startID": 1594194095740
}
}
```
*Sample result*:
```json
{
"result": [
[1594194095746, 1485369733217, -1, 3, 4, -60, 2500, 6157, 0],
[1594201393292, 1485369902086, -1, 1, -60, -60, 0, 4846, 0]
],
"error": null
}
```
* **getLatestReviewID**
Returns the unix time of the latest review for the given deck. 0 if no review has ever been made for the deck.
*Sample request*:
```json
{
"action": "getLatestReviewID",
"version": 6,
"params": {
"deck": "default"
}
}
```
*Sample result*:
```json
{
"result": 1594194095746,
"error": null
}
```
* **insertReviews**
Inserts the given reviews into the database. Required format: list of 9-tuples `(reviewTime, cardID, usn, buttonPressed, newInterval, previousInterval, newFactor, reviewDuration, reviewType)`
*Sample request*:
```json
{
"action": "insertReviews",
"version": 6,
"params": {
"reviews": [
[1594194095746, 1485369733217, -1, 3, 4, -60, 2500, 6157, 0],
[1594201393292, 1485369902086, -1, 1, -60, -60, 0, 4846, 0]
]
}
}
```
*Sample result*:
```json
{
"result": null,
"error": null
}
```

View File

@ -34,6 +34,7 @@ import anki.storage
import aqt
from anki.exporting import AnkiPackageExporter
from anki.importing import AnkiPackageImporter
from anki.utils import joinFields, intTime, guid64, fieldChecksum
from . import web, util
@ -877,13 +878,20 @@ class AnkiConnect:
'question': util.getQuestion(card),
'answer': util.getAnswer(card),
'modelName': model['name'],
'ord': card.ord,
'deckName': self.deckNameFromId(card.did),
'css': model['css'],
'factor': card.factor,
#This factor is 10 times the ease percentage,
# so an ease of 310% would be reported as 3100
'interval': card.ivl,
'note': card.nid
'note': card.nid,
'type': card.type,
'queue': card.queue,
'due': card.due,
'reps': card.reps,
'lapses': card.lapses,
'left': card.left,
})
except TypeError as e:
# Anki will give a TypeError if the card ID does not exist.
@ -895,6 +903,68 @@ class AnkiConnect:
return result
@util.api()
def cardReviews(self, deck, startID):
return self.database().all("select id, cid, usn, ease, ivl, lastIvl, factor, time, type from revlog "
"where id>? and cid in (select id from cards where did=?)",
startID, self.decks().id(deck))
@util.api()
def reloadCollection(self):
self.collection().reset()
@util.api()
def getLatestReviewID(self, deck):
return self.database().scalar("select max(id) from revlog where cid in (select id from cards where did=?)",
self.decks().id(deck)) or 0
@util.api()
def updateCompleteDeck(self, data):
self.startEditing()
did = self.decks().id(data["deck"])
self.decks().flush()
model_manager = self.collection().models
for _, card in data["cards"].items():
self.database().execute(
"replace into cards (id, nid, did, ord, type, queue, due, ivl, factor, reps, lapses, left, "
"mod, usn, odue, odid, flags, data) "
"values (" + "?," * (12 + 6 - 1) + "?)",
card["id"], card["nid"], did, card["ord"], card["type"], card["queue"], card["due"],
card["ivl"], card["factor"], card["reps"], card["lapses"], card["left"],
intTime(), -1, 0, 0, 0, 0
)
note = data["notes"][str(card["nid"])]
tags = self.collection().tags.join(self.collection().tags.canonify(note["tags"]))
self.database().execute(
"replace into notes(id, mid, tags, flds,"
"guid, mod, usn, flags, data, sfld, csum) values (" + "?," * (4 + 7 - 1) + "?)",
note["id"], note["mid"], tags, joinFields(note["fields"]),
guid64(), intTime(), -1, 0, 0, "", fieldChecksum(note["fields"][0])
)
model = data["models"][str(note["mid"])]
if not model_manager.get(model["id"]):
model_o = model_manager.new(model["name"])
for field_name in model["fields"]:
field = model_manager.newField(field_name)
model_manager.addField(model_o, field)
for template_name in model["templateNames"]:
template = model_manager.newTemplate(template_name)
model_manager.addTemplate(model_o, template)
model_o["id"] = model["id"]
model_manager.update(model_o)
model_manager.flush()
self.stopEditing()
@util.api()
def insertReviews(self, reviews):
if len(reviews) == 0: return
sql = "insert into revlog(id,cid,usn,ease,ivl,lastIvl,factor,time,type) values "
for row in reviews:
sql += "(%s)," % ",".join(map(str, row))
sql = sql[:-1]
self.database().execute(sql)
@util.api()
def notesInfo(self, notes):
result = []

View File

@ -67,5 +67,32 @@ class TestDecks(unittest.TestCase):
deckConfigId = util.invoke('cloneDeckConfigId', cloneFrom=deckConfigId, name='test')
self.assertFalse(deckConfigId)
# updateCompleteDeck
util.invoke("updateCompleteDeck", data={
"deck": "test3",
"cards": {
"12": {
"id": 12, "nid": 23, "ord": 0, "type": 0, "queue": 0,
"due": 1186031, "factor": 0, "ivl": 0, "reps": 0, "lapses": 0, "left": 0
}
},
"notes": {
"23": {
"id": 23, "mid": 34, "fields": ["frontValue", "backValue"], "tags": ["aTag"]
}
},
"models": {
"34": {
"id": 34, "fields": ["Front", "Back"], "templateNames": ["Card 1"], "name": "anotherModel",
}
}
})
deckNames = util.invoke("deckNames")
self.assertIn("test3", deckNames)
cardIDs = util.invoke('findCards', query='deck:test3')
self.assertEqual(len(cardIDs), 1)
self.assertEqual(cardIDs[0], 12)
if __name__ == '__main__':
unittest.main()

View File

@ -52,6 +52,9 @@ class TestMisc(unittest.TestCase):
deckNames = util.invoke('deckNames')
self.assertIn(deckName, deckNames)
# reloadCollection
util.invoke("reloadCollection")
if __name__ == '__main__':
unittest.main()

View File

@ -6,7 +6,15 @@ import unittest
import util
class TestMisc(unittest.TestCase):
class TestStats(unittest.TestCase):
def setUp(self):
util.invoke('createDeck', deck='test')
note = {'deckName': 'test', 'modelName': 'Basic', 'fields': {'Front': 'front1', 'Back': 'back1'}, 'tags': ['tag1']}
self.noteId = util.invoke('addNote', note=note)
def tearDown(self):
util.invoke('deleteDecks', decks=['test'], cardsToo=True)
def runTest(self):
# getNumCardsReviewedToday
result = util.invoke('getNumCardsReviewedToday')
@ -16,6 +24,20 @@ class TestMisc(unittest.TestCase):
result = util.invoke('getCollectionStatsHTML')
self.assertIsInstance(result, str)
# no reviews for new deck
self.assertEqual(len(util.invoke("cardReviews", deck="test", startID=0)), 0)
self.assertEqual(util.invoke("getLatestReviewID", deck="test"), 0)
# add reviews
cardId = int(util.invoke('findCards', query='deck:test')[0])
latestID = 123456 # small enough to not interfere with existing reviews
util.invoke("insertReviews", reviews=[
[latestID-1, cardId, -1, 3, 4, -60, 2500, 6157, 0],
[latestID, cardId, -1, 1, -60, -60, 0, 4846, 0]
])
self.assertEqual(len(util.invoke("cardReviews", deck="test", startID=0)), 2)
self.assertEqual(util.invoke("getLatestReviewID", deck="test"), latestID)
if __name__ == '__main__':
unittest.main()