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:
parent
84586cb352
commit
ce0cc7dce3
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
```
|
@ -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
|
||||
}
|
||||
```
|
@ -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
|
||||
}
|
||||
```
|
||||
|
@ -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 = []
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user