def test_localsync_deck(): # deck two was modified last assert deck2.modified > deck1.modified d2mod = deck2.modified assert deck1.lastSync == 0 and deck2.lastSync == 0 client.sync() assert deck1.modified == deck2.modified assert deck1.lastSync == deck1.modified assert deck1.lastSync == deck2.lastSync # ensure values are being synced deck1.lowPriority += u",foo" deck1.updateAllPriorities() deck1.setModified() client.sync() assert "foo" in deck2.lowPriority assert deck1.modified == deck2.modified assert deck1.lastSync == deck2.lastSync deck2.description = u"newname" deck2.setModified() client.sync() assert deck1.description == u"newname" # the most recent change should take precedence deck1.description = u"foo" deck1.setModified() deck2.description = u"bar" deck2.setModified() client.sync() assert deck1.description == "bar" # answer a card to ensure stats & history are copied c = deck1.getCard() deck1.answerCard(c, 4) client.sync() assert dailyStats(deck2.s).reps == 1 assert globalStats(deck2.s).reps == 1 assert deck2.s.scalar("select count(id) from reviewHistory") == 1
def test_localsync_deck(): # deck two was modified last assert deck2.modified > deck1.modified d2mod = deck2.modified assert deck1.lastSync == 0 and deck2.lastSync == 0 client.sync() assert deck1.modified == deck2.modified assert deck1.lastSync == deck1.modified assert deck1.lastSync == deck2.lastSync # ensure values are being synced deck1.lowPriority += u",foo" deck1.updateAllPriorities() deck1.setModified() client.sync() assert "foo" in deck2.lowPriority assert deck1.modified == deck2.modified assert deck1.lastSync == deck2.lastSync deck2.description = u"newname" deck2.setModified() client.sync() assert deck1.description == u"newname" # the most recent change should take precedence deck1.description = u"foo" deck1.setModified() deck2.description = u"bar" deck2.setModified() client.sync() assert deck1.description == "bar" # answer a card to ensure stats & history are copied c = deck1.getCard() deck1.answerCard(c, 4) client.sync() assert dailyStats(deck2).reps == 1 assert globalStats(deck2).reps == 1 assert deck2.s.scalar("select count(*) from reviewHistory") == 1 # make sure meta data is synced deck1.setVar("foo", 1) assert deck1.getInt("foo") == 1 assert deck2.getInt("foo") is None client.sync() assert deck1.getInt("foo") == 1 assert deck2.getInt("foo") == 1
def preSyncRefresh(self): # ensure global stats are available (queue may not be built) self.deck._globalStats = globalStats(self.deck)
def procSync(inputData): json = {} json['error'] = 0 json['exception'] = 'Unset' try: data = simplejson.loads(inputData) #print >> sys.stderr, "request" #pretty.pretty(data) try: if data['method'] == 'getdeck': ui.logMsg('Sync started') #json['deck'] = ui.ankiQt.syncName json['deck'] = ui.sync_names elif data['method'] == 'realsync': # ToDo: Error if syncName doesn't match the current deck ui.logMsg('Syncing deck ' + data['syncName']) #deck = DeckStorage.Deck(ui.ankiQt.deckPath) deck = DeckStorage.Deck(ui.sync_paths[ui.sync_names.index(data['syncName'])]) try: deck.rebuildQueue() # Apply client updates if len(data['reviewHistory']) > 0: ui.logMsg(' Applying %d new reviews' % len(data['reviewHistory'])) timeSave = time.time thinkingTimeSave = cards.Card.thinkingTime totalTimeSave = cards.Card.totalTime numApplied = 0 numDropped = 0 try: def timeOverride(): return review['time'] def thinkingTimeOverride(self): return review['thinkingTime'] try: deck._globalStats = globalStats(deck) deck._dailyStats = dailyStats(deck) except: deck._globalStats = globalStats(deck.s) deck._dailyStats = dailyStats(deck.s) time.time = timeOverride cards.Card.thinkingTime = thinkingTimeOverride cards.Card.totalTime = thinkingTimeOverride for review in data['reviewHistory']: try: card = deck.cardFromId(int(review['cardId'])) if review['reps'] == card.reps + 1: # Apply only in order reps deck.answerCard(card, review['ease']) numApplied += 1 else: numDropped += 1 except: numDropped += 1 finally: time.time = timeSave cards.Card.thinkingTime = thinkingTimeSave cards.Card.totalTime = totalTimeSave if numApplied > 0: # Update anything that may have changed deck.modified = time.time() deck.save() if numDropped > 0: ui.logMsg(' Dropped %d stale reviews' % numDropped) # Send host updates json['lastSyncHost'] = time.time() global update update = {} cardFields = tables['cards'] cardIdIndex = cardFields.index('id') cardFactIdIndex = cardFields.index('factId') cardModifiedIndex = cardFields.index('modified') factFields = tables['facts'] factIdIndex = factFields.index('id') factModelIdIndex = factFields.index('modelId') factModifiedIndex = factFields.index('modified') modelFields = tables['models'] modelIdIndex = modelFields.index('id') modelModifiedIndex = modelFields.index('modified') # Get cards to review for the next 2 days, up to maxCards maxCards = ui.sync_cards gotCards = 0 pickCards = [] pickCardsIds = set() checkTime = time.time() prevTime = 0 hourSkip = 4 for hour in range(0, ui.sync_days * 24, hourSkip): if gotCards == maxCards: break # First pick due failed cards if deck.revCardOrder == 0: dueOrder = 'priority desc, interval desc' elif deck.revCardOrder == 1: dueOrder = 'priority desc, interval' elif deck.revCardOrder == 2: dueOrder = 'priority desc, combinedDue' else: dueOrder = 'priority desc, factId, ordinal' failedCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 0 AND combinedDue >= %f AND combinedDue <= %f AND priority != 0 \ ORDER BY %s LIMIT %d' % (getFieldList(tables['cards']), prevTime, checkTime, dueOrder, maxCards-gotCards)) for c in failedCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 # Next pick due review cards reviewCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 1 AND combinedDue >= %f AND combinedDue <= %f AND priority != 0 \ ORDER BY priority desc, interval desc LIMIT %d' % (getFieldList(tables['cards']), prevTime, checkTime, maxCards-gotCards)) for c in reviewCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 prevTime = checkTime - 60 checkTime += 60 * 60 * hourSkip # Finally pick new cards if gotCards < maxCards: if deck.newCardOrder == 0: # random newCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 2 \ ORDER BY priority desc, factId, ordinal LIMIT %d' % (getFieldList(tables['cards']), maxCards - gotCards)) else: # ordered newCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 2 \ ORDER BY priority desc, due LIMIT %d' % (getFieldList(tables['cards']), maxCards - gotCards)) for c in newCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 #print >> sys.stderr, 'Sync', len(pickCards) haveCards = set() for i in data['cardIds']: haveCards.add(int(i)) haveFacts = set() for i in data['factIds']: haveFacts.add(int(i)) haveModels = set() for i in data['modelIds']: haveModels.add(int(i)) updateCards = [[],[],[]] # modify, add, remove updateFacts = [[],[],[]] # modify, add, remove updateModels = [[],[],[]] # modify, add, remove pickFacts = {} for card in pickCards: cardId = card[cardIdIndex] if cardId in haveCards: if card[cardModifiedIndex] > data['lastSyncHost']: updateCards[0].append( procRow(cardFields, card, True) ) # Modify haveCards.remove(cardId) else: updateCards[1].append( procRow(cardFields, card, False) ) # Add pickFacts[card[cardFactIdIndex]] = None pickModels = {} for factId in pickFacts.keys(): fact = deck.s.first('SELECT %s FROM facts WHERE id=%d' % (getFieldList(factFields), factId)) if factId in haveFacts: if fact[factModifiedIndex] > data['lastSyncHost']: updateFacts[0].append( procRow(factFields, fact, True) ) # Modify haveFacts.remove(factId) else: updateFacts[1].append( procRow(factFields, fact, False) ) # Add pickModels[fact[factModelIdIndex]] = None for modelId in pickModels.keys(): model = deck.s.first('SELECT %s FROM models WHERE id=%d' % (getFieldList(modelFields), modelId)) if modelId in haveModels: if model[modelModifiedIndex] > data['lastSyncHost']: updateModels[0].append( procRow(modelFields, model, True) ) # Modify haveModels.remove(modelId) else: updateModels[1].append( procRow(modelFields, model, False) ) # Add # Mark the remaining items for removal for x in haveCards: updateCards[2].append(str(x)) for x in haveFacts: updateFacts[2].append(str(x)) for x in haveModels: updateModels[2].append(str(x)) update['cards'] = {'modified': updateCards[0], 'added': updateCards[1], 'removed': updateCards[2]} update['facts'] = {'modified': updateFacts[0], 'added': updateFacts[1], 'removed': updateFacts[2]} update['models'] = {'modified': updateModels[0], 'added': updateModels[1], 'removed': updateModels[2]} addedDeck = deck.s.all('SELECT %s FROM %s WHERE created > %f' % (getFieldList(tables['decks']), 'decks', data['lastSyncHost'])) modifiedDeck = deck.s.all('SELECT %s FROM %s WHERE modified > %f and created <= %f' % (getFieldList(tables['decks']), 'decks', data['lastSyncHost'], data['lastSyncHost'])) update['decks'] = { 'modified': [procRow(tables['decks'], x, True) for x in modifiedDeck], 'added': [procRow(tables['decks'], x, False) for x in addedDeck], 'removed': [] } for t in tables.keys(): json[t+'_sql_insert'] = 'INSERT INTO ' + t + ' (' + getFieldList(tables[t]) + ') VALUES (' + getValueList(tables[t]) + ')' json[t+'_sql_update'] = 'UPDATE ' + t + ' SET ' + getSetList(tables[t]) + ' WHERE id = ?' json['numUpdates'] = countUpdates() json['updates'] = getUpdate(200) #printUpdate(json['updates']) #ui.logMsg(' Sending %d items' % (json['updates']['numUpdates'])) finally: deck.rebuildCounts() deck.modified = time.time() deck.save() deck.close() #ui.logMsg('Sync complete') elif data['method'] == 'nextsync': json['updates'] = getUpdate(200) #printUpdate(json['updates']) #ui.logMsg(' Sending %d items' % (json['updates']['numUpdates'])) else: json['error'] = 1 json['exception'] = 'Invalid method:' + data['method'] finally: pass #finally: # pass except Exception, e: json['error'] = 1 json['exception'] = str(e) print >> sys.stderr, "Exception", e ui.logMsg('There were errors during sync.')
def procSync(inputData): json = {} json['error'] = 0 json['exception'] = 'Unset' try: data = simplejson.loads(inputData) #print >> sys.stderr, "request" #pretty.pretty(data) try: if data['method'] == 'getdeck': ui.logMsg(' ') #json['deck'] = ui.ankiQt.syncName json['deck'] = ui.sync_names elif data['method'] == 'realsync': # ToDo: Error if syncName doesn't match the current deck ui.logMsg('Syncing deck ' + data['syncName']) #deck = DeckStorage.Deck(ui.ankiQt.deckPath) deck = DeckStorage.Deck(ui.sync_paths[ui.sync_names.index(data['syncName'])]) if deck == None: json['error'] = 1 json['exception'] = "Could not open deck: %s" % data['syncName']; ui.logMsg("Could not open deck: %s" % data['syncName']) res = simplejson.dumps(json, ensure_ascii=False) return res old_requeueCard = deck.requeueCard deck.requeueCard = dummy_requeueCard try: #deck.rebuildQueue() # Apply client updates if len(data['reviewHistory']) > 0: ui.logMsg(' Applying %d new reviews' % len(data['reviewHistory'])) timeSave = time.time thinkingTimeSave = cards.Card.thinkingTime totalTimeSave = cards.Card.totalTime numApplied = 0 numDropped = 0 try: def timeOverride(): return review['time'] def thinkingTimeOverride(self): return review['thinkingTime'] try: deck._globalStats = globalStats(deck) deck._dailyStats = dailyStats(deck) except: deck._globalStats = globalStats(deck.s) deck._dailyStats = dailyStats(deck.s) time.time = timeOverride cards.Card.thinkingTime = thinkingTimeOverride cards.Card.totalTime = thinkingTimeOverride for review in data['reviewHistory']: try: card = deck.cardFromId(int(review['cardId'])) if review['reps'] == card.reps + 1: # Apply only in order reps deck.answerCard(card, review['ease']) numApplied += 1 else: numDropped += 1 except: numDropped += 1 finally: time.time = timeSave cards.Card.thinkingTime = thinkingTimeSave cards.Card.totalTime = totalTimeSave if numApplied > 0: # Update anything that may have changed deck.modified = time.time() deck.save() if numDropped > 0: ui.logMsg(' Dropped %d stale reviews' % numDropped) # Send host updates json['lastSyncHost'] = time.time() global update update = {} cardFields = tables['cards'] cardIdIndex = cardFields.index('id') cardFactIdIndex = cardFields.index('factId') cardModifiedIndex = cardFields.index('modified') cardModelIdIndex = cardFields.index('cardModelId') cardQuestionIndex = cardFields.index('question') cardAnswerIndex = cardFields.index('answer') factFields = tables['facts'] factIdIndex = factFields.index('id') factModelIdIndex = factFields.index('modelId') factModifiedIndex = factFields.index('modified') modelFields = tables['models'] modelIdIndex = modelFields.index('id') modelModifiedIndex = modelFields.index('modified') # Get cards to review for the next 2 days, up to maxCards maxCards = ui.sync_cards gotCards = 0 pickCards = [] pickCardsIds = set() checkTime = time.time() prevTime = 0 hourSkip = 4 for hour in range(0, ui.sync_days * 24, hourSkip): if gotCards == maxCards: break # First pick due failed cards if deck.revCardOrder == 0: dueOrder = 'priority desc, interval desc' elif deck.revCardOrder == 1: dueOrder = 'priority desc, interval' elif deck.revCardOrder == 2: dueOrder = 'priority desc, combinedDue' else: dueOrder = 'priority desc, factId, ordinal' failedCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 0 AND combinedDue >= %f AND combinedDue <= %f AND priority != 0 \ ORDER BY %s LIMIT %d' % (getFieldList(tables['cards']), prevTime, checkTime, dueOrder, maxCards-gotCards)) for c in failedCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 # Next pick due review cards reviewCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 1 AND combinedDue >= %f AND combinedDue <= %f AND priority != 0 \ ORDER BY priority desc, interval desc LIMIT %d' % (getFieldList(tables['cards']), prevTime, checkTime, maxCards-gotCards)) for c in reviewCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 prevTime = checkTime - 60 checkTime += 60 * 60 * hourSkip # Finally pick new cards if gotCards < maxCards: if deck.newCardOrder == 0: # random newCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 2 \ ORDER BY priority desc, factId, ordinal LIMIT %d' % (getFieldList(tables['cards']), maxCards - gotCards)) else: # ordered newCards = deck.s.all('SELECT %s FROM cards WHERE \ type = 2 \ ORDER BY priority desc, due LIMIT %d' % (getFieldList(tables['cards']), maxCards - gotCards)) for c in newCards: cardId = c[cardIdIndex] if cardId not in pickCardsIds: pickCardsIds.add(cardId) pickCards.append(c) gotCards += 1 #print >> sys.stderr, 'Sync', len(pickCards) haveCards = set() for i in data['cardIds']: haveCards.add(int(i)) haveFacts = set() for i in data['factIds']: haveFacts.add(int(i)) haveModels = set() for i in data['modelIds']: haveModels.add(int(i)) updateCards = [[],[],[]] # modify, add, remove updateFacts = [[],[],[]] # modify, add, remove updateModels = [[],[],[]] # modify, add, remove try: deckName = os.path.basename(deck.path) mediaDir = re.sub("(?i)\.(anki)$", ".media/", deckName) except: mediaDir = "media/" mediaDir = "" soundRe = '\[sound:([^\Z]+?)\]' soundSub = '<embed target="myself" type="audio/mpeg" loop="true" height="50" width="50" href="%s\\1"></embed>' % mediaDir imageRe = '\[image:([^\Z]+?)\]' imageSub = 'image:%s/\\1' % mediaDir pickTemp = pickCards pickCards = [] for c in pickTemp: # convert rows to lists c = [x for x in c] # get html formatted q&a card = deck.s.query(cards.Card).get(c[cardIdIndex]) q = card.htmlQuestion() a = card.htmlAnswer() try: q = runFilter("drawQuestion", q, card) a = runFilter("drawAnswer", a, card) except: pass #[sound:0f69b0f7e968546b4dce403c5386a07d.mp3] q = re.sub(soundRe, soundSub, q) a = re.sub(soundRe, soundSub, a) q = re.sub(imageRe, imageSub, q) a = re.sub(imageRe, imageSub, a) c[cardQuestionIndex] = q c[cardAnswerIndex] = a pickCards.append(c) pickFacts = {} for card in pickCards: cardId = card[cardIdIndex] if cardId in haveCards: if card[cardModifiedIndex] > data['lastSyncHost']: updateCards[0].append( procRow(cardFields, card, True) ) # Modify haveCards.remove(cardId) else: updateCards[1].append( procRow(cardFields, card, False) ) # Add pickFacts[card[cardFactIdIndex]] = None pickModels = {} for factId in pickFacts.keys(): fact = deck.s.first('SELECT %s FROM facts WHERE id=%d' % (getFieldList(factFields), factId)) if factId in haveFacts: if fact[factModifiedIndex] > data['lastSyncHost']: updateFacts[0].append( procRow(factFields, fact, True) ) # Modify haveFacts.remove(factId) else: updateFacts[1].append( procRow(factFields, fact, False) ) # Add pickModels[fact[factModelIdIndex]] = None for modelId in pickModels.keys(): model = deck.s.first('SELECT %s FROM models WHERE id=%d' % (getFieldList(modelFields), modelId)) if modelId in haveModels: if model[modelModifiedIndex] > data['lastSyncHost']: updateModels[0].append( procRow(modelFields, model, True) ) # Modify haveModels.remove(modelId) else: updateModels[1].append( procRow(modelFields, model, False) ) # Add # Mark the remaining items for removal for x in haveCards: updateCards[2].append(str(x)) for x in haveFacts: updateFacts[2].append(str(x)) for x in haveModels: updateModels[2].append(str(x)) update['cards'] = {'modified': updateCards[0], 'added': updateCards[1], 'removed': updateCards[2]} update['facts'] = {'modified': updateFacts[0], 'added': updateFacts[1], 'removed': updateFacts[2]} update['models'] = {'modified': updateModels[0], 'added': updateModels[1], 'removed': updateModels[2]} addedDeck = deck.s.all('SELECT %s FROM %s WHERE created > %f' % (getFieldList(tables['decks']), 'decks', data['lastSyncHost'])) modifiedDeck = deck.s.all('SELECT %s FROM %s WHERE modified > %f and created <= %f' % (getFieldList(tables['decks']), 'decks', data['lastSyncHost'], data['lastSyncHost'])) update['decks'] = { 'modified': [procRow(tables['decks'], x, True) for x in modifiedDeck], 'added': [procRow(tables['decks'], x, False) for x in addedDeck], 'removed': [] } for t in tables.keys(): json[t+'_sql_insert'] = 'INSERT INTO ' + t + ' (' + getFieldList(tables[t]) + ') VALUES (' + getValueList(tables[t]) + ')' json[t+'_sql_update'] = 'UPDATE ' + t + ' SET ' + getSetList(tables[t]) + ' WHERE id = ?' json['numUpdates'] = countUpdates() json['updates'] = getUpdate(200) css = deck.css try: css = runFilter("addStyles", css, None) except: pass css = re.sub("font-size:([0-9]+)px", changeFontScale, css) json['deckcss'] = css #ui.logMsg(' css\n %s' % json['deckcss']) #printUpdate(json['updates']) #ui.logMsg(' Sending %d items' % (json['updates']['numUpdates'])) finally: deck.requeueCard = old_requeueCard deck.rebuildCounts() deck.modified = time.time() deck.save() deck.close() #ui.logMsg('Sync complete') elif data['method'] == 'nextsync': json['updates'] = getUpdate(200) #printUpdate(json['updates']) #ui.logMsg(' Sending %d items' % (json['updates']['numUpdates'])) else: json['error'] = 1 json['exception'] = 'Invalid method:' + data['method'] finally: pass #finally: # pass except: json['error'] = 1 json['exception'] = traceback.format_exc() print >> sys.stderr, json['exception'] ui.logMsg('There were errors during sync.') #print >> sys.stderr, "response" #pretty.pretty(json) res = simplejson.dumps(json, ensure_ascii=False) return res