def test_saveAs(): path = "/tmp/test_saveAs.anki" try: os.unlink(path) except OSError: pass path2 = "/tmp/test_saveAs2.anki" try: os.unlink(path2) except OSError: pass # start with an in-memory deck deck = DeckStorage.Deck() deck.addModel(BasicModel()) # add a card f = deck.newFact() f['Front'] = u"foo" f['Back'] = u"bar" deck.addFact(f) assert deck.cardCount == 1 # save in new deck newDeck = deck.saveAs(path) assert newDeck.cardCount == 1 # delete card id = newDeck.s.scalar("select id from cards") newDeck.deleteCard(id) # save into new deck newDeck2 = newDeck.saveAs(path2) # new deck should have zero cards assert newDeck2.cardCount == 0 # but old deck should have reverted the unsaved changes newDeck = DeckStorage.Deck(path) assert newDeck.cardCount == 1 newDeck.close()
def test_anki10_modtime(): deck1 = DeckStorage.Deck() deck2 = DeckStorage.Deck() client = SyncClient(deck1) server = SyncServer(deck2) client.setServer(server) deck1.addModel(BasicModel()) f = deck1.newFact() f['Front'] = u"foo"; f['Back'] = u"bar" deck1.addFact(f) assert deck1.cardCount == 1 assert deck2.cardCount == 0 client.sync() assert deck1.cardCount == 1 assert deck2.cardCount == 1 file_ = unicode(os.path.join(testDir, "importing/test10-3.anki")) file = "/tmp/test10-3.anki" shutil.copy(file_, file) i = anki10.Anki10Importer(deck1, file) i.doImport() client.sync() assert i.total == 1 assert deck2.s.scalar("select count(*) from cards") == 2 assert deck2.s.scalar("select count(*) from facts") == 2 assert deck2.s.scalar("select count(*) from models") == 2
def setup_local(loadDecks=None): global deck1, deck2, client, server if loadDecks: deck1 = DeckStorage.Deck(loadDecks[0], backup=False) deck2 = DeckStorage.Deck(loadDecks[1], backup=False) else: deck1 = DeckStorage.Deck() deck1.addModel(BasicModel()) deck1.currentModel.cardModels[1].active = True deck1.newCardOrder = 1 f = deck1.newFact() f['Front'] = u"foo" f['Back'] = u"bar" f.tags = u"foo" deck1.addFact(f) deck2 = DeckStorage.Deck() deck2.addModel(BasicModel()) deck2.currentModel.cardModels[1].active = True f = deck2.newFact() f['Front'] = u"baz" f['Back'] = u"qux" f.tags = u"bar" deck2.addFact(f) deck2.newCardOrder = 1 client = SyncClient(deck1) server = SyncServer(deck2) client.setServer(server)
def test_attachReadOnly(): # non-writeable dir assertException(Exception, lambda: DeckStorage.Deck("/attachroot")) # reuse tmp file from before, test non-writeable file os.chmod(newPath, 0) assertException(Exception, lambda: DeckStorage.Deck(newPath)) os.chmod(newPath, 0666) os.unlink(newPath)
def test_localsync_threeway(): # deck1 (client) <-> deck2 (server) <-> deck3 (client) deck3 = DeckStorage.Deck() client2 = SyncClient(deck3) server2 = SyncServer(deck2) client2.setServer(server2) client.sync() client2.sync() # add a new question f = deck1.newFact() f['Front'] = u"a" f['Back'] = u"b" f = deck1.addFact(f) card = f.cards[0] client.sync() assert deck1.cardCount == 6 assert deck2.cardCount == 6 # check it propagates from server to deck3 client2.sync() assert deck3.cardCount == 6 # delete a card on deck1 deck1.deleteCard(card.id) client.sync() assert deck1.cardCount == 5 assert deck2.cardCount == 5 # make sure the delete is now propagated from the server to deck3 client2.sync() assert deck3.cardCount == 5
def test_mnemosyne10(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) file = unicode(os.path.join(testDir, "importing/test.mem")) i = mnemosyne10.Mnemosyne10Importer(deck, file) i.doImport() assert i.total == 5 deck.s.close()
def test_export_anki(): oldTime = deck.modified e = AnkiExporter(deck) newname = unicode(tempfile.mkstemp(prefix="ankitest")[1]) os.unlink(newname) e.exportInto(newname) assert deck.modified == oldTime # connect to new deck d2 = DeckStorage.Deck(newname, backup=False) assert d2.cardCount == 4 # try again, limited to a tag newname = unicode(tempfile.mkstemp(prefix="ankitest")[1]) os.unlink(newname) e.limitTags = ['tag'] e.exportInto(newname) d2 = DeckStorage.Deck(newname, backup=False) assert d2.cardCount == 2
def test_dingsbums(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) startNumberOfFacts = deck.factCount file = unicode(os.path.join(testDir, "importing/dingsbums.xml")) i = dingsbums.DingsBumsImporter(deck, file) i.doImport() assert 7 == i.total deck.s.close()
def test_csv(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) file = unicode(os.path.join(testDir, "importing/text-2fields.txt")) i = csvfile.TextImporter(deck, file) i.doImport() # four problems - missing front, dupe front, wrong num of fields assert len(i.log) == 4 assert i.total == 5 deck.s.close()
def test_csv_tags(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) file = unicode(os.path.join(testDir, "importing/text-tags.txt")) i = csvfile.TextImporter(deck, file) i.doImport() facts = deck.s.query(Fact).all() assert len(facts) == 2 assert facts[0].tags == "baz qux" or facts[1].tags == "baz qux" deck.s.close()
def test_supermemo_xml_01_unicode(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) file = unicode(os.path.join(testDir, "importing/supermemo1.xml")) i = supermemo_xml.SupermemoXmlImporter(deck, file) #i.META.logToStdOutput = True i.doImport() # only returning top-level elements? assert i.total == 1 deck.s.close()
def render_get_download(self, params): global deck global config global ANKIMINI_PATH import tempfile name = params["deck"] self.wfile.write(""" <html> <head><title>Downloading %s ...</title></head> <body style="font-family: arial, helvetica;"> """ % (name)) buffer = "" tmp_dir = None try: if deck: deck.save() local_deck = expandName(name, '.anki') if os.path.exists(local_deck): raise Exception( "Local deck %s already exists. You can't overwrite, sorry!" % (name, )) tmp_dir = unicode( tempfile.mkdtemp(dir=ANKIMINI_PATH, prefix="anki"), sys.getfilesystemencoding()) tmp_deck = expandName(name, '.anki', tmp_dir) newdeck = ds.Deck(tmp_deck) newdeck.s.execute("pragma cache_size = 1000") newdeck.modified = 0 newdeck.s.commit() newdeck.syncName = unicode(name) newdeck.lastLoaded = newdeck.modified newdeck = self.syncDeck(newdeck) newdeck.save() if deck: deck.close() deck = None newdeck.close() os.rename(tmp_deck, local_deck) config['DECK_PATH'] = local_deck config.saveConfig() deck = openDeck() except Exception, e: buffer += "<em>Download failed!</em><br />" buffer += str(e) print "render_get_download(): exception: " + str(e) import traceback traceback.print_exc()
def test_modelChange(): deck = DeckStorage.Deck() m = Model(u"Japanese") m1 = m f = FieldModel(u'Expression', True, True) m.addFieldModel(f) m.addFieldModel(FieldModel(u'Meaning', False, False)) f = FieldModel(u'Reading', False, False) m.addFieldModel(f) m.addCardModel( CardModel(u"Recognition", u"%(Expression)s", u"%(Reading)s<br>%(Meaning)s")) m.addCardModel( CardModel(u"Recall", u"%(Meaning)s", u"%(Expression)s<br>%(Reading)s", active=False)) m.tags = u"Japanese" m1.cardModels[1].active = True deck.addModel(m1) f = deck.newFact() f['Expression'] = u'e' f['Meaning'] = u'm' f['Reading'] = u'r' f = deck.addFact(f) f2 = deck.newFact() f2['Expression'] = u'e2' f2['Meaning'] = u'm2' f2['Reading'] = u'r2' deck.addFact(f2) m2 = BasicModel() m2.cardModels[1].active = True deck.addModel(m2) # convert to basic assert deck.modelUseCount(m1) == 2 assert deck.modelUseCount(m2) == 0 assert deck.cardCount == 4 assert deck.factCount == 2 fmap = { m1.fieldModels[0]: m2.fieldModels[0], m1.fieldModels[1]: None, m1.fieldModels[2]: m2.fieldModels[1] } cmap = {m1.cardModels[0]: m2.cardModels[0], m1.cardModels[1]: None} deck.changeModel([f.id], m2, fmap, cmap) assert deck.modelUseCount(m1) == 1 assert deck.modelUseCount(m2) == 1 assert deck.cardCount == 3 assert deck.factCount == 2 (q, a) = deck.s.first(""" select question, answer from cards where factId = :id""", id=f.id) assert stripHTML(q) == u"e" assert stripHTML(a) == u"r"
def test_factAddDelete(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) # set rollback point deck.s.commit() f = deck.newFact() # empty fields try: deck.addFact(f) except Exception, e: pass
def test_modelAddDelete(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) deck.addModel(BasicModel()) f = deck.newFact() f['Front'] = u'1' f['Back'] = u'2' deck.addFact(f) assert deck.cardCount == 1 deck.deleteModel(deck.currentModel) assert deck.cardCount == 0 deck.s.refresh(deck)
def test_anki10(): # though these are not modified, sqlite updates the mtime, so copy to tmp # first file_ = unicode(os.path.join(testDir, "importing/test10.anki")) file = "/tmp/test10.anki" shutil.copy(file_, file) file2_ = unicode(os.path.join(testDir, "importing/test10-2.anki")) file2 = "/tmp/test10-2.anki" shutil.copy(file2_, file2) deck = DeckStorage.Deck() i = anki10.Anki10Importer(deck, file) i.doImport() assert i.total == 2 deck.s.rollback() deck.close() # import a deck into itself - 10-2 is the same as test10, but with one # card answered and another deleted. nothing should be synced to client deck = DeckStorage.Deck(file, backup=False) i = anki10.Anki10Importer(deck, file2) i.doImport() assert i.total == 0 deck.s.rollback()
def setup1(): global deck deck = DeckStorage.Deck() deck.addModel(BasicModel()) deck.currentModel.cardModels[1].active = True f = deck.newFact() f['Front'] = u"foo" f['Back'] = u"bar" f.tags = u"tag, tag2" deck.addFact(f) f = deck.newFact() f['Front'] = u"baz" f['Back'] = u"qux" deck.addFact(f)
def test_attachNew(): global newPath, newModified path = "/tmp/test_attachNew.anki" try: os.unlink(path) except OSError: pass deck = DeckStorage.Deck(path) # for attachOld() newPath = deck.path deck.setVar("pageSize", 4096) deck.save() newModified = deck.modified deck.close() del deck
def openDeck(deckPath=None): global config try: if deckPath is None: deckPath = config['DECK_PATH'] deckPath = expandName(deckPath, '.anki') print "open deck.. " + deckPath if not os.path.exists(deckPath): raise ValueError("Couldn't find deck %s" % (deckPath, )) deck = ds.Deck(deckPath, backup=False) deck.s.execute("pragma cache_size = 1000") except Exception, e: print "Error loading deck" print e deck = None
def test_media(): deck = DeckStorage.Deck() # create a media dir deck.mediaDir(create=True) # put a file into it file = unicode(os.path.join(testDir, "deck/fake.png")) deck.addMedia(file) # make sure it gets copied on saveas path = "/tmp/saveAs2.anki" sum = "0bee89b07a248e27c83fc3d5951213c1.png" try: os.unlink(path) except OSError: pass deck.saveAs(path) assert os.path.exists("/tmp/saveAs2.media/%s" % sum)
def test_findCards(): deck = DeckStorage.Deck() deck.addModel(BasicModel()) f = deck.newFact() f['Front'] = u'dog' f['Back'] = u'cat' f.tags = u"monkey" deck.addFact(f) f = deck.newFact() f['Front'] = u'goats are fun' f['Back'] = u'sheep' f.tags = u"sheep goat horse" deck.addFact(f) f = deck.newFact() f['Front'] = u'cat' f['Back'] = u'sheep' deck.addFact(f) assert not deck.findCards("tag:donkey") assert len(deck.findCards("tag:sheep")) == 1 assert len(deck.findCards("tag:sheep tag:goat")) == 1 assert len(deck.findCards("tag:sheep tag:monkey")) == 0 assert len(deck.findCards("tag:monkey")) == 1 assert len(deck.findCards("tag:sheep -tag:monkey")) == 1 assert len(deck.findCards("-tag:sheep")) == 2 assert len(deck.findCards("cat")) == 2 assert len(deck.findCards("cat -dog")) == 1 assert len(deck.findCards("cat -dog")) == 1 assert len(deck.findCards("are goats")) == 1 assert len(deck.findCards('"are goats"')) == 0 assert len(deck.findCards('"goats are"')) == 1 # make sure card templates and models match too assert len(deck.findCards('tag:basic')) == 3 assert len(deck.findCards('tag:forward')) == 3 deck.addModel(BasicModel()) f = deck.newFact() f['Front'] = u'foo' f['Back'] = u'bar' deck.addFact(f) deck.currentModel.cardModels[1].active = True f = deck.newFact() f['Front'] = u'baz' f['Back'] = u'qux' c = deck.addFact(f) assert len(deck.findCards('tag:forward')) == 5 assert len(deck.findCards('tag:reverse')) == 1
def test_modelCopy(): deck = DeckStorage.Deck() m = BasicModel() assert len(m.fieldModels) == 2 assert len(m.cardModels) == 2 deck.addModel(m) f = deck.newFact() f['Front'] = u'1' deck.addFact(f) m2 = deck.copyModel(m) assert m2.name == "Basic copy" assert m2.id != m.id assert m2.fieldModels[0].id != m.fieldModels[0].id assert m2.cardModels[0].id != m.cardModels[0].id assert len(m2.fieldModels) == 2 assert len(m.fieldModels) == 2 assert len(m2.fieldModels) == len(m.fieldModels) assert len(m.cardModels) == 2 assert len(m2.cardModels) == 2
def exportInto(self, path): n = 3 if not self.includeSchedulingInfo: n += 1 self.deck.startProgress(n) self.deck.updateProgress(_("Exporting...")) try: os.unlink(path) except (IOError, OSError): pass self.newDeck = DeckStorage.Deck(path) client = SyncClient(self.deck) server = SyncServer(self.newDeck) client.setServer(server) client.localTime = self.deck.modified client.remoteTime = 0 self.deck.s.flush() # set up a custom change list and sync lsum = self.localSummary() rsum = server.summary(0) self.deck.updateProgress() payload = client.genPayload((lsum, rsum)) self.deck.updateProgress() res = server.applyPayload(payload) if not self.includeSchedulingInfo: self.deck.updateProgress() self.newDeck.s.statement(""" delete from reviewHistory""") self.newDeck.s.statement(""" update cards set interval = 0, lastInterval = 0, due = created, lastDue = 0, factor = 2.5, firstAnswered = 0, reps = 0, successive = 0, averageTime = 0, reviewTime = 0, youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0, youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0, matureEase3 = 0, matureEase4 = 0, yesCount = 0, noCount = 0, spaceUntil = 0, type = 2, relativeDelay = 2, combinedDue = created, modified = :now """, now=time.time()) self.newDeck.s.statement(""" delete from stats""") # media if self.includeMedia: server.deck.mediaPrefix = "" copyLocalMedia(client.deck, server.deck) # need to save manually self.newDeck.rebuildCounts() self.newDeck.updateAllPriorities() self.exportedCards = self.newDeck.cardCount self.newDeck.utcOffset = -1 self.newDeck.s.commit() self.newDeck.close() self.deck.finishProgress()
minSync = min(localSync, remoteSync) self.conflictResolution = None if (localMod != remoteMod and minSync > 0 and localMod > minSync and remoteMod > minSync): self.emit(SIGNAL("syncConflicts"), syncName) while not self.conflictResolution: time.sleep(0.2) if self.conflictResolution == "cancel": # alert we're finished early self.emit(SIGNAL("syncFinished")) return -1 # reopen self.setStatus(_("Syncing <b>%s</b>...") % syncName, 0) self.deck = None try: self.deck = DeckStorage.Deck(path) disable = False if deck and not self.deck.syncName: # multi-mode sync and syncing has been disabled by upgrade disable = True client = SyncClient(self.deck) client.setServer(proxy) # need to do anything? start = time.time() if client.prepareSync(proxy.timediff) and not disable: if self.deck.lastSync <= 0: if client.remoteTime > client.localTime: self.conflictResolution = "keepRemote" else: self.conflictResolution = "keepLocal" changes = True
def syncDeck(self, deck): try: proxy = HttpSyncServerProxy(config.get('SYNC_USERNAME'), config.get('SYNC_PASSWORD')) proxy.connect("ankimini") except: raise Exception("Can't sync - check username/password") if not proxy.hasDeck(deck.syncName): raise Exception("Can't sync, no deck on server") if abs(proxy.timestamp - time.time()) > 60: raise Exception("Your clock is off by more than 60 seconds.<br>" \ "Syncing will not work until you fix this.") client = SyncClient(deck) client.setServer(proxy) # need to do anything? proxy.deckName = deck.syncName if not client.prepareSync(): raise Exception("Nothing to do") self.flushWrite("""<h1>Syncing deck</h1> <h2>%s</h2> <em>This could take a while with a big deck ... please be patient!</em> """ % (deck.path, )) # hack to get safari to render immediately! self.flushWrite("<!--" + " " * 1024 + "-->") # this can take a long time ... ensure the client doesn't timeout before we finish from threading import Event, Thread ping_event = Event() def ping_client(s=self.wfile, ev=ping_event): while 1: ev.wait(3) if ev.isSet(): return s.write(".<!--\n-->") s.flush() ping_thread = Thread(target=ping_client) ping_thread.start() # summary self.lineWrite("Fetching summary from server..") sums = client.summaries() needFull = client.needFullSync(sums) if needFull: self.lineWrite("Doing full sync..") client.fullSync() else: # diff self.lineWrite("Determining differences..") payload = client.genPayload(sums) # send payload pr = client.payloadChangeReport(payload) self.lineWrite("<br>" + pr + "<br>") self.lineWrite("Sending payload...") if needFull: deck = ds.Deck(deck.path, backup=False) # why is deck.syncName getting lost on a full sync??? if deck.syncName is None: deck.syncName = proxy.deckName print "syncName was lost on full sync, restored to", deck.syncName else: res = client.server.applyPayload(payload) # apply reply self.lineWrite("Applying reply..") client.applyPayloadReply(res) # finished. save deck, preserving mod time self.lineWrite("Sync complete.") deck.rebuildQueue() deck.lastLoaded = deck.modified deck.s.flush() deck.s.commit() # turn off client ping ping_event.set() ping_thread.join(5) return deck
# -*- coding: utf-8 -*- # Copyright: Damien Elmes <*****@*****.**> # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html from time import time from anki import DeckStorage as ds print "open deck.." t = time() d = ds.Deck("tango.anki") print "opened in", time() - t #d.newCardsPerDay = 1000000 # timing for i in range(5000): t3 = time() #print "get..", t = time() card = d.getCard(orm=False) #cards = d.getCards() # for (k,v) in cards.items(): # for x in v: # print k, x #print [c.id for c in cards] #print "getc", time() - t t = time() #print "ans..", d.answerCard(card, 2) #print time() - t; t = time() s = d.getStats()
def doImport(self): "Import." random = self.deck.newCardOrder == NEW_CARDS_RANDOM num = 4 if random: num += 1 self.deck.startProgress(num) self.deck.updateProgress(_("Importing...")) src = DeckStorage.Deck(self.file, backup=False) client = SyncClient(self.deck) server = SyncServer(src) client.setServer(server) # if there is a conflict, sync local -> src client.localTime = self.deck.modified client.remoteTime = 0 src.s.execute("update facts set modified = 1") src.s.execute("update models set modified = 1") src.s.execute("update cards set modified = 1") src.s.execute("update media set created = 1") self.deck.s.flush() # set up a custom change list and sync lsum = client.summary(0) self._clearDeleted(lsum) rsum = server.summary(0) self._clearDeleted(rsum) payload = client.genPayload((lsum, rsum)) # no need to add anything to src payload['added-models'] = [] payload['added-cards'] = [] payload['added-facts'] = {'facts': [], 'fields': []} assert payload['deleted-facts'] == [] assert payload['deleted-cards'] == [] assert payload['deleted-models'] == [] self.deck.updateProgress() res = server.applyPayload(payload) self.deck.updateProgress() client.applyPayloadReply(res) copyLocalMedia(server.deck, client.deck) # add tags self.deck.updateProgress() fids = [f[0] for f in res['added-facts']['facts']] self.deck.addTags(fids, self.tagsToAdd) # mark import material as newly added self.deck.s.statement("update cards set modified = :t where id in %s" % ids2str([x[0] for x in res['added-cards']]), t=time.time()) self.deck.s.statement( "update facts set modified = :t where id in %s" % ids2str([x[0] for x in res['added-facts']['facts']]), t=time.time()) self.deck.s.statement( "update models set modified = :t where id in %s" % ids2str([x['id'] for x in res['added-models']]), t=time.time()) # update total and refresh self.total = len(res['added-facts']['facts']) src.s.rollback() src.engine.dispose() # randomize? if random: self.deck.updateProgress() self.deck.randomizeNewCards([x[0] for x in res['added-cards']]) self.deck.flushMod() self.deck.finishProgress()
def test_new(): deck = DeckStorage.Deck() assert not deck.path assert deck.engine assert deck.modified
def test_attachOld(): deck = DeckStorage.Deck(newPath, backup=False) assert deck.modified == newModified deck.close()
def doImport(self): """Totally overrides the method in Importer""" num = 7 # the number of updates to progress bar (see references in method endElement in DingsBumsHandler self.deck.startProgress(num) self.deck.updateProgress(_("Importing...")) # parse the DingsBums?! xml file handler = DingsBumsHandler(self.deck) saxparser = make_parser() saxparser.setContentHandler(handler) saxparser.parse(self.file) self.total = handler.countFacts self.deck.finishProgress() self.deck.setModified() if __name__ == '__main__': print "Starting ..." # for testing you can start it standalone. Use an argument to specify the file to import filename = str(sys.argv[1]) mydeck = DeckStorage.Deck() i = DingsBumsImporter(mydeck, filename) i.doImport() assert 7 == i.total mydeck.s.close() print "... Finished" sys.exit(1)