1245 lines
47 KiB
Python
1245 lines
47 KiB
Python
from direct.directnotify.DirectNotifyGlobal import directNotify
|
|
from direct.showbase.DirectObject import DirectObject
|
|
|
|
from direct.fsm.FSM import FSM
|
|
from direct.fsm.StatePush import StateVar, FunctionCall
|
|
|
|
from direct.task import Task
|
|
|
|
from toontown.coderedemption.TTCodeDict import TTCodeDict
|
|
from toontown.coderedemption import TTCodeRedemptionConsts
|
|
from toontown.coderedemption import TTCodeRedemptionDBConsts
|
|
|
|
from direct.showbase.Job import Job
|
|
|
|
import os
|
|
import json
|
|
import datetime
|
|
import random
|
|
|
|
|
|
from otp.ai.AIBaseGlobal import *
|
|
|
|
class TryAgainLater(Exception):
|
|
def __init__(self, exception, file):
|
|
self._exception = exception
|
|
self._file = file
|
|
|
|
def getJSONException(self):
|
|
return self._exception
|
|
|
|
def __str__(self):
|
|
return 'problem using JSON file %s, try again later (%s)' % (self._file, self.exception)
|
|
|
|
class TTCodeRedemptionDBTester(Job):
|
|
notify = directNotify.newCategory('TTCodeRedemptionDBTester')
|
|
|
|
class TestRewarder:
|
|
def _giveReward(self, avId, rewardTypeId, rewardItemId, callback):
|
|
callback(0)
|
|
|
|
def __init__(self, db):
|
|
self._db = db
|
|
Job.__init__(self, 'TTCodeRedemptionDBTester-%s' % serialNum())
|
|
|
|
def getRandomSamples(self, callback, numSamples):
|
|
samples = []
|
|
for i in range(numSamples):
|
|
samples.append(int(random.random() * ((1 << 32)-1)))
|
|
callback(samples)
|
|
|
|
@classmethod
|
|
def isLotNameValid(cls, lotName):
|
|
# make sure a user doesn't create a lot that matches the test lot naming convention
|
|
return (TTCodeRedemptionDBConsts.TestLotName not in lotName)
|
|
|
|
@classmethod
|
|
def cleanup(cls, db):
|
|
# remove any leftover data from previous tests
|
|
db._testing = True
|
|
lotNames = db.getLotNames()
|
|
for lotName in lotNames:
|
|
if TTCodeRedemptionDBConsts.TestLotName in lotName:
|
|
db.deleteLot(lotName)
|
|
db._testing = False
|
|
|
|
def _handleRedeemResult(self, result, awardMgrResult):
|
|
self._redeemResult.append(result)
|
|
self._redeemResult.append(awardMgrResult)
|
|
|
|
def _getUnusedLotName(self):
|
|
lotNames = self._db.getLotNames()
|
|
while 1:
|
|
lotName = '%s%s' % (TTCodeRedemptionDBConsts.TestLotName, int(random.random() * ((1 << 32)-1)))
|
|
if lotName not in lotNames:
|
|
break
|
|
return lotName
|
|
|
|
def _getUnusedManualCode(self):
|
|
while 1:
|
|
code = ''
|
|
length = random.randrange(4, 16)
|
|
manualCharIndex = random.randrange(length)
|
|
for i in range(length):
|
|
if i == manualCharIndex:
|
|
charSet = TTCodeDict.ManualOnlyCharacters
|
|
else:
|
|
charSet = TTCodeDict.ManualCharacters
|
|
char = random.choice(charSet)
|
|
if char in TTCodeDict.IgnoredManualCharacters:
|
|
i -= 1
|
|
code = code + char
|
|
if not self._db.codeExists(code):
|
|
break
|
|
return code
|
|
|
|
def _getUnusedUtf8ManualCode(self):
|
|
chars = '\u65e5\u672c\u8a9e'
|
|
code = str('')
|
|
while 1:
|
|
code += random.choice(chars)
|
|
if not self._db.codeExists(code):
|
|
break
|
|
return code
|
|
|
|
def run(self):
|
|
self.notify.info('testing started')
|
|
|
|
retryStartT = None
|
|
retryDelay = 5
|
|
|
|
while 1:
|
|
try:
|
|
db = self._db
|
|
db._testing = True
|
|
|
|
lotName = self._getUnusedLotName()
|
|
|
|
# make sure there are at least one manual and one auto lot throughout the tests
|
|
phLots = []
|
|
phLots.append(self._getUnusedLotName())
|
|
for i in self._db.createLot(self.getRandomSamples, phLots[-1], 1, 0, 0):
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
phLots.append(self._getUnusedLotName())
|
|
code = self._getUnusedManualCode()
|
|
self._db.createManualLot(phLots[-1], code, 0, 0)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# lot creation
|
|
NumCodes = 3
|
|
RewardType = 0
|
|
RewardItemId = 0
|
|
ExpirationDate = '9999-04-01'
|
|
for i in self._db.createLot(self.getRandomSamples, lotName, NumCodes, RewardType, RewardItemId, ExpirationDate):
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
lotNames = self._db.getLotNames()
|
|
if lotName not in lotNames:
|
|
self.notify.error('could not create code redemption lot \'%s\'' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
autoLotNames = self._db.getAutoLotNames()
|
|
if lotName not in autoLotNames:
|
|
self.notify.error('auto lot \'%s\' not found in getAutoLotNames()' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
manualLotNames = self._db.getManualLotNames()
|
|
if lotName in manualLotNames:
|
|
self.notify.error('auto lot \'%s\' found in getAutoLotNames()' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# get codes in lot
|
|
codes = self._db.getCodesInLot(lotName)
|
|
if len(codes) != NumCodes:
|
|
self.notify.error('incorrect number of codes from getCodesInLot (%s)' % len(codes))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# code existance query
|
|
exists = self._db.codeExists(codes[0])
|
|
if not exists:
|
|
self.notify.error('codeExists returned false for code %s' % codes[0])
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (not yet redeemed)
|
|
redemptions = self._db.getRedemptions(codes[0])
|
|
if redemptions != 0:
|
|
self.notify.error('incorrect number of redemptions (%s) for not-yet-redeemed code %s' % (redemptions, codes[0], ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# get lot name from code
|
|
ln = self._db.getLotNameFromCode(codes[0])
|
|
if ln != lotName:
|
|
self.notify.error('incorrect lot name (%s) from code (%s)' % (ln, codes[0]))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# get reward from code
|
|
rt, rid = self._db.getRewardFromCode(codes[0])
|
|
if rt != RewardType or rid != RewardItemId:
|
|
self.notify.error('incorrect reward (%s, %s) from code %s' % (rt, rid))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# redeem code
|
|
self._redeemResult = []
|
|
self._db.redeemCode(codes[0], TTCodeRedemptionDBConsts.FakeAvId, self.TestRewarder(), self._handleRedeemResult)
|
|
if self._redeemResult[0] or self._redeemResult[1]:
|
|
self.notify.error('error redeeming code %s for fake avatar %s: %s' % (codes[0], TTCodeRedemptionDBConsts.FakeAvId, self._redeemResult))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (redeemed)
|
|
redemptions = self._db.getRedemptions(codes[0])
|
|
if redemptions != 1:
|
|
self.notify.error('incorrect number of redemptions (%s) for already-redeemed code %s' % (redemptions, codes[0], ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# redeem code that has already been redeemed
|
|
self._redeemResult = []
|
|
self._db.redeemCode(codes[0], TTCodeRedemptionDBConsts.FakeAvId, self.TestRewarder(), self._handleRedeemResult)
|
|
if self._redeemResult[0] != TTCodeRedemptionConsts.RedeemErrors.CodeAlreadyRedeemed:
|
|
self.notify.error('able to redeem code %s twice' % (codes[0]))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (redeemed)
|
|
redemptions = self._db.getRedemptions(codes[0])
|
|
if redemptions != 1:
|
|
self.notify.error('incorrect number of redemptions (%s) for already-redeemed code %s' % (redemptions, codes[0], ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# lookup codes redeemed by avId
|
|
c = self._db.lookupCodesRedeemedByAvId(TTCodeRedemptionDBConsts.FakeAvId)
|
|
if len(c) != 1:
|
|
self.notify.error('lookupCodesRedeemedByAvId returned wrong number of codes: %s' % c)
|
|
if c[0] != codes[0]:
|
|
self.notify.error('lookupCodesRedeemedByAvId returned wrong code: %s' % c[0])
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# get code details
|
|
details = self._db.getCodeDetails(codes[0])
|
|
if details[TTCodeRedemptionDBConsts.CodeFieldName] != codes[0]:
|
|
self.notify.error('incorrect %s (%s) returned by getCodeDetails(%s)' % (TTCodeRedemptionDBConsts.CodeFieldName, details[TTCodeRedemptionDBConsts.CodeFieldName], codes[0]))
|
|
if details[TTCodeRedemptionDBConsts.AvatarIdFieldName] != TTCodeRedemptionDBConsts.FakeAvId:
|
|
self.notify.error('incorrect %s (%s) returned by getCodeDetails(%s)' % (TTCodeRedemptionDBConsts.AvatarIdFieldName, details[TTCodeRedemptionDBConsts.AvatarIdFieldName], codes[0]))
|
|
if details[TTCodeRedemptionDBConsts.RewardTypeFieldName] != RewardType:
|
|
self.notify.error('incorrect %s (%s) returned by getCodeDetails(%s)' % (TTCodeRedemptionDBConsts.RewardTypeFieldName, details[TTCodeRedemptionDBConsts.RewardTypeFieldName], codes[0]))
|
|
if details[TTCodeRedemptionDBConsts.RewardItemIdFieldName] != RewardItemId:
|
|
self.notify.error('incorrect %s (%s) returned by getCodeDetails(%s)' % (TTCodeRedemptionDBConsts.RewardItemIdFieldName, details[TTCodeRedemptionDBConsts.RewardItemIdFieldName], codes[0]))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# get expiration date
|
|
exp = self._db.getExpiration(lotName)
|
|
if exp != ExpirationDate:
|
|
self.notify.error('incorrect expiration date: %s' % exp)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# change expiration date
|
|
y = 1111
|
|
m = 4
|
|
d = 1
|
|
NewExp = '%s-%02d-%02d' % (y, m, d)
|
|
assert datetime.datetime.fromtimestamp(time.time()) > datetime.datetime(y, m, d)
|
|
|
|
# make sure it doesn't change the expiration date of all lots
|
|
controlLotName = self._getUnusedLotName()
|
|
controlCode = self._getUnusedManualCode()
|
|
controlExp = '%s-%02d-%02d' % (y, m, d+1)
|
|
self._db.createManualLot(controlLotName, controlCode, RewardType, RewardItemId, expirationDate=controlExp)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
self._db.setExpiration(lotName, NewExp)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
exp = self._db.getExpiration(lotName)
|
|
if (exp != NewExp):
|
|
self.notify.error('could not change expiration date for lot %s' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
cExp = self._db.getExpiration(controlLotName)
|
|
if (cExp != controlExp):
|
|
self.notify.error('setExpiration changed control lot expiration!')
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
self._db.deleteLot(controlLotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# redeem code that is expired
|
|
self._redeemResult = []
|
|
self._db.redeemCode(codes[1], TTCodeRedemptionDBConsts.FakeAvId, self.TestRewarder(), self._handleRedeemResult)
|
|
if self._redeemResult[0] != TTCodeRedemptionConsts.RedeemErrors.CodeIsExpired:
|
|
self.notify.error('expired code %s was not flagged upon redeem' % (codes[1]))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# lot deletion
|
|
self._db.deleteLot(lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
codes = (self._getUnusedManualCode(), self._getUnusedUtf8ManualCode())
|
|
|
|
for code in codes:
|
|
# manual code lot
|
|
lotName = self._getUnusedLotName()
|
|
self.notify.info('manual code: %s' % (code))
|
|
self._db.createManualLot(lotName, code, RewardType, RewardItemId)
|
|
if not self._db.lotExists(lotName):
|
|
self.notify.error('could not create manual lot %s' % lotName)
|
|
if not self._db.codeExists(code):
|
|
self.notify.error('could not create manual code %s' % code)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
autoLotNames = self._db.getAutoLotNames()
|
|
if lotName in autoLotNames:
|
|
self.notify.error('manual lot \'%s\' found in getAutoLotNames()' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
manualLotNames = self._db.getManualLotNames()
|
|
if lotName not in manualLotNames:
|
|
self.notify.error('manual lot \'%s\' not found in getAutoLotNames()' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (not-yet-redeemed)
|
|
redemptions = self._db.getRedemptions(code)
|
|
if redemptions != 0:
|
|
self.notify.error('incorrect number of redemptions (%s) for not-yet-redeemed code %s' % (redemptions, code, ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# redeem manually-created code
|
|
self._redeemResult = []
|
|
self._db.redeemCode(code, TTCodeRedemptionDBConsts.FakeAvId, self.TestRewarder(), self._handleRedeemResult)
|
|
if self._redeemResult[0] or self._redeemResult[1]:
|
|
self.notify.error('error redeeming code %s for fake avatar %s: %s' % (code, TTCodeRedemptionDBConsts.FakeAvId, self._redeemResult))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (not-yet-redeemed)
|
|
self._db.commitOutstandingRedemptions()
|
|
redemptions = self._db.getRedemptions(code)
|
|
if redemptions != 1:
|
|
self.notify.error('incorrect number of redemptions (%s) for redeemed code %s' % (redemptions, code, ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# redeem manually-created code again
|
|
self._redeemResult = []
|
|
self._db.redeemCode(code, TTCodeRedemptionDBConsts.FakeAvId, self.TestRewarder(), self._handleRedeemResult)
|
|
if self._redeemResult[0] or self._redeemResult[1]:
|
|
self.notify.error('error redeeming code %s again for fake avatar %s: %s' % (code, TTCodeRedemptionDBConsts.FakeAvId, self._redeemResult))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# number of redemptions (not-yet-redeemed)
|
|
self._db.commitOutstandingRedemptions()
|
|
redemptions = self._db.getRedemptions(code)
|
|
if redemptions != 2:
|
|
self.notify.error('incorrect number of redemptions (%s) for twice-redeemed code %s' % (redemptions, code, ))
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
self._db.deleteLot(lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
lotNames = self._db.getLotNames()
|
|
if lotName in lotNames:
|
|
self.notify.error('could not delete code redemption lot \'%s\'' % lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
# remove placeholder lots
|
|
for lotName in phLots:
|
|
self._db.deleteLot(lotName)
|
|
db._testing = False
|
|
yield None
|
|
db._testing = True
|
|
|
|
break
|
|
except TryAgainLater as e:
|
|
self.notify.warning('caught TryAgainLater exception during self-test, retrying')
|
|
retryStartT = globalClock.getRealTime()
|
|
while globalClock.getRealTime() < (retryStartT + retryDelay):
|
|
yield None
|
|
retryDelay *= 2
|
|
|
|
self.notify.info('testing done')
|
|
db._testing = False
|
|
yield Job.Done
|
|
|
|
|
|
class NotFound:
|
|
pass
|
|
|
|
|
|
class InfoCache:
|
|
NotFound = NotFound
|
|
|
|
def __init__(self):
|
|
self._cache = {}
|
|
|
|
def clear(self):
|
|
self._cache = {}
|
|
|
|
def cacheInfo(self, key, info):
|
|
self._cache[key] = info
|
|
|
|
def hasInfo(self, key):
|
|
return key in self._cache
|
|
|
|
def getInfo(self, key):
|
|
return self._cache.get(key, NotFound)
|
|
|
|
class TTCodeRedemptionDB(DirectObject):
|
|
notify = directNotify.newCategory('TTCodeRedemptionDB')
|
|
|
|
TryAgainLater = TryAgainLater
|
|
|
|
DoSelfTest = ConfigVariableBool('code-redemption-self-test', False).getValue()
|
|
|
|
# optimization that reads in all codes and maps them to their lot
|
|
# if the code set gets too large this might use up too much RAM
|
|
# you can disable the optimization by turning this config off
|
|
CacheAllCodes = ConfigVariableBool('code-redemption-cache-all-codes', True).getValue()
|
|
|
|
class LotFilter:
|
|
All = 'all'
|
|
Redeemable = 'redeemable'
|
|
NonRedeemable = 'nonRedeemable'
|
|
Redeemed = 'redeemed'
|
|
Expired = 'expired'
|
|
|
|
def __init__(self, air):
|
|
self.air = air
|
|
|
|
self.filePath = ConfigVariableString('code-redemption-data-folder', 'data/code_redemption/').getValue()
|
|
self.lotsFileName = ConfigVariableString('code-redemption-lots-file', 'lots').getValue()
|
|
self.codeSpaceFileName = ConfigVariableString('code-redemption-code-space-file', 'code_space').getValue()
|
|
self.codeSetFileName = ConfigVariableString('code-redemption-code-set-file', 'code_set_%s').getValue()
|
|
|
|
# lot name cache
|
|
self._code2lotNameCache = InfoCache()
|
|
self._lotName2manualCache = InfoCache()
|
|
self._code2rewardCache = InfoCache()
|
|
self.doMethodLater(5 * 60, self._cacheClearTask, uniqueName('clearLotNameCache'))
|
|
|
|
self._manualCode2outstandingRedemptions = {}
|
|
self.doMethodLater(1 * 60, self._updateRedemptionsTask, uniqueName('updateRedemptions'))
|
|
|
|
self._code2lotName = {}
|
|
|
|
# set to true while doing internal tests
|
|
self._testing = False
|
|
self._initializedSV = StateVar(False)
|
|
self._startTime = globalClock.getRealTime()
|
|
self._doingCleanup = False
|
|
self._dbInitRetryTimeout = 5
|
|
self._doInitialCleanup()
|
|
|
|
self._refreshCode2lotName()
|
|
|
|
def _doInitialCleanup(self, task=None):
|
|
if not self._initializedSV.get():
|
|
self._doCleanup()
|
|
|
|
if not self._initializedSV.get():
|
|
self.doMethodLater(self._dbInitRetryTimeout, self._doInitialCleanup,
|
|
uniqueName('codeRedemptionInitialCleanup'))
|
|
|
|
self._dbInitRetryTimeout *= 2
|
|
self.notify.warning('could not initialize MySQL db, trying again later...')
|
|
return Task.done
|
|
|
|
def _doCleanup(self):
|
|
if self._doingCleanup:
|
|
return
|
|
|
|
self._doingCleanup = True
|
|
|
|
if not self._initializedSV.get():
|
|
try:
|
|
TTCodeRedemptionDBTester.cleanup(self)
|
|
except TryAgainLater as e:
|
|
pass
|
|
else:
|
|
self._initializedSV.set(True)
|
|
|
|
self._doingCleanup = False
|
|
|
|
def _randFuncCallback(self, randList, randSamplesOnOrder, samples):
|
|
randSamplesOnOrder[0] -= len(samples)
|
|
randList.extend(samples)
|
|
|
|
def _refreshCode2lotName(self):
|
|
if not self.CacheAllCodes:
|
|
return
|
|
|
|
# update the dict of code -> lotName for all codes
|
|
self._code2lotName = {}
|
|
lotNames = self.getLotNames()
|
|
|
|
for lotName in lotNames:
|
|
codes = self.getCodesInLot(lotName)
|
|
|
|
for code in codes:
|
|
self._code2lotName[code] = lotName
|
|
|
|
@staticmethod
|
|
def _getExpirationString(expiration):
|
|
"""
|
|
formats expiration date for JSON
|
|
"""
|
|
return '%s 23:59:59' % str(expiration)
|
|
|
|
@staticmethod
|
|
def _getNowString():
|
|
nowStr = str(datetime.datetime.fromtimestamp(time.time()))
|
|
# leave off the fractional seconds
|
|
if '.' in nowStr:
|
|
nowStr = nowStr[:nowStr.index('.')]
|
|
return nowStr
|
|
|
|
def createManualLot(self, name, code, rewardType, rewardItemId, expirationDate=None):
|
|
self.notify.info('creating manual code lot \'%s\', code=%s' % (name, (code), ))
|
|
self._doCleanup()
|
|
|
|
code = TTCodeDict.getFromReadableCode(code)
|
|
|
|
if self.lotExists(name):
|
|
self.notify.error('tried to create lot %s that already exists' % name)
|
|
|
|
if self.codeExists(code):
|
|
self.notify.error('tried to create code %s that already exists' % (code))
|
|
|
|
# First load lots file
|
|
lotsFile = self.getFileName(self.lotsFileName)
|
|
lotsData = self.loadLotsFile(lotsFile)
|
|
|
|
lotId = lotsData[TTCodeRedemptionDBConsts.NextLotIdFieldName]
|
|
|
|
lot = {
|
|
TTCodeRedemptionDBConsts.LotIdFieldName: lotId,
|
|
TTCodeRedemptionDBConsts.NameFieldName: name,
|
|
TTCodeRedemptionDBConsts.ManualFieldName: True,
|
|
TTCodeRedemptionDBConsts.RewardTypeFieldName: rewardType,
|
|
TTCodeRedemptionDBConsts.RewardItemIdFieldName: rewardItemId,
|
|
TTCodeRedemptionDBConsts.SizeFieldName: 1,
|
|
TTCodeRedemptionDBConsts.CreationFieldName: self._getNowString()
|
|
}
|
|
|
|
if expirationDate:
|
|
lot[TTCodeRedemptionDBConsts.ExpirationFieldName] = self._getExpirationString(expirationDate)
|
|
else:
|
|
lot[TTCodeRedemptionDBConsts.ExpirationFieldName] = ''
|
|
|
|
lotsData[TTCodeRedemptionDBConsts.LotsFieldName].append(lot)
|
|
|
|
lotsData[TTCodeRedemptionDBConsts.NextLotIdFieldName] = lotId + 1
|
|
|
|
# Then load Code Set file
|
|
codeSetFile = self.getFileName(self.codeSetFileName % (name))
|
|
codeSetData = self.loadCodeSetFile(codeSetFile)
|
|
|
|
codeSet = {
|
|
TTCodeRedemptionDBConsts.CodeFieldName: code,
|
|
TTCodeRedemptionDBConsts.LotIdFieldName: lotId,
|
|
TTCodeRedemptionDBConsts.RedemptionsFieldName: 0
|
|
}
|
|
|
|
codeSetData.append(codeSet)
|
|
|
|
self.saveFile(lotsFile, lotsData)
|
|
self.saveFile(codeSetFile, codeSetData)
|
|
|
|
self._refreshCode2lotName()
|
|
|
|
self.notify.info('done')
|
|
|
|
def createLot(self, randFunc, name, numCodes, rewardType, rewardItemId, expirationDate=None):
|
|
"""
|
|
generator, yields None while working, yields True when finished
|
|
randFunc must take a callback and a number of random samples, and must call the callback
|
|
with a list of random 32-bit values of length equal to that specified in the call to randFunc
|
|
the random values must be truly random and non-repeatable (see NonRepeatableRandomSource)
|
|
"""
|
|
self.notify.info('creating code lot \'%s\', %s codes' % (name, numCodes, ))
|
|
self._doCleanup()
|
|
|
|
if self.lotExists(name):
|
|
self.notify.error('tried to create lot %s that already exists' % name)
|
|
|
|
randSampleRequestSize = ConfigVariableInt('code-redemption-rand-request-size', 50).getValue()
|
|
randSampleRequestThreshold = 2 * randSampleRequestSize
|
|
randSamples = []
|
|
randSamplesOnOrder = [0, ]
|
|
|
|
requestSize = min(numCodes, randSampleRequestSize)
|
|
randSamplesOnOrder[0] += requestSize
|
|
randFunc(Functor(self._randFuncCallback, randSamples, randSamplesOnOrder), requestSize)
|
|
|
|
# First load code space file
|
|
codeSpaceFile = self.getFileName(self.codeSpaceFileName)
|
|
codeSpaceData = self.loadCodeSpaceFile(codeSpaceFile)
|
|
|
|
codeLength = codeSpaceData[TTCodeRedemptionDBConsts.CodeLengthFieldName]
|
|
nextCodeValue = codeSpaceData[TTCodeRedemptionDBConsts.NextCodeValueFieldName]
|
|
|
|
startSerialNum = nextCodeValue
|
|
|
|
# Second load lots file
|
|
lotsFile = self.getFileName(self.lotsFileName)
|
|
lotsData = self.loadLotsFile(lotsFile)
|
|
|
|
lotId = lotsData[TTCodeRedemptionDBConsts.NextLotIdFieldName]
|
|
|
|
lot = {
|
|
TTCodeRedemptionDBConsts.LotIdFieldName: lotId,
|
|
TTCodeRedemptionDBConsts.NameFieldName: name,
|
|
TTCodeRedemptionDBConsts.ManualFieldName: False,
|
|
TTCodeRedemptionDBConsts.RewardTypeFieldName: rewardType,
|
|
TTCodeRedemptionDBConsts.RewardItemIdFieldName: rewardItemId,
|
|
TTCodeRedemptionDBConsts.SizeFieldName: numCodes,
|
|
TTCodeRedemptionDBConsts.CreationFieldName: self._getNowString()
|
|
}
|
|
|
|
if expirationDate:
|
|
lot[TTCodeRedemptionDBConsts.ExpirationFieldName] = self._getExpirationString(expirationDate)
|
|
else:
|
|
lot[TTCodeRedemptionDBConsts.ExpirationFieldName] = ''
|
|
|
|
lotsData[TTCodeRedemptionDBConsts.LotsFieldName].append(lot)
|
|
|
|
lotsData[TTCodeRedemptionDBConsts.NextLotIdFieldName] = lotId + 1
|
|
|
|
# Then load Code Set file
|
|
codeSetFile = self.getFileName(self.codeSetFileName % (name))
|
|
codeSetData = self.loadCodeSetFile(codeSetFile)
|
|
|
|
codesLeft = numCodes
|
|
curSerialNum = startSerialNum
|
|
numCodeValues = TTCodeDict.getNumUsableValuesInCodeSpace(codeLength)
|
|
n = 0
|
|
while codesLeft:
|
|
numCodesRequested = (len(randSamples) + randSamplesOnOrder[0])
|
|
if numCodesRequested < codesLeft:
|
|
if numCodesRequested < randSampleRequestThreshold:
|
|
requestSize = min(codesLeft, randSampleRequestSize)
|
|
randSamplesOnOrder[0] += requestSize
|
|
randFunc(Functor(self._randFuncCallback, randSamples, randSamplesOnOrder), requestSize)
|
|
|
|
if len(randSamples) == 0:
|
|
yield None
|
|
continue
|
|
|
|
# r in [0,1) but truly random (non-repeatable)
|
|
r = randSamples.pop(0) / float(1 << 32)
|
|
assert 0. <= r < 1.
|
|
# this produces the 1 in N chance of guessing a correct code
|
|
# each code is given a chunk of code space, of size N, and the actual value of the
|
|
# code is chosen from that section of code space using a true random source
|
|
# that means there's no way to guess a valid code based on observation of other codes
|
|
randScatter = int(r * TTCodeDict.BruteForceFactor)
|
|
assert 0 <= randScatter < TTCodeDict.BruteForceFactor
|
|
value = (curSerialNum * TTCodeDict.BruteForceFactor) + randScatter
|
|
obfValue = TTCodeDict.getObfuscatedCodeValue(value, codeLength)
|
|
code = TTCodeDict.getCodeFromValue(obfValue, codeLength)
|
|
|
|
codeSet = {
|
|
TTCodeRedemptionDBConsts.CodeFieldName: code,
|
|
TTCodeRedemptionDBConsts.LotIdFieldName: lotId,
|
|
TTCodeRedemptionDBConsts.RedemptionsFieldName: 0
|
|
}
|
|
|
|
codeSetData.append(codeSet)
|
|
|
|
codesLeft -= 1
|
|
curSerialNum += 1
|
|
if curSerialNum >= numCodeValues:
|
|
curSerialNum = 0
|
|
codeLength += 1
|
|
numCodeValues = TTCodeDict.getNumUsableValuesInCodeSpace(codeLength)
|
|
|
|
n = n + 1
|
|
if (n % 100) == 0:
|
|
yield None
|
|
|
|
codeSpaceData[TTCodeRedemptionDBConsts.CodeLengthFieldName] = codeLength
|
|
codeSpaceData[TTCodeRedemptionDBConsts.NextCodeValueFieldName] = curSerialNum
|
|
|
|
self.saveFile(codeSpaceFile, codeSpaceData)
|
|
self.saveFile(lotsFile, lotsData)
|
|
self.saveFile(codeSetFile, codeSetData)
|
|
|
|
self._refreshCode2lotName()
|
|
|
|
self.notify.info('done')
|
|
yield True
|
|
|
|
def deleteLot(self, lotName):
|
|
self.notify.info('deleting code lot \'%s\'' % (lotName, ))
|
|
self._doCleanup()
|
|
|
|
self._clearCaches()
|
|
|
|
self.deleteFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
lotsFile = self.getFileName(self.lotsFileName)
|
|
lotsData = self.loadLotsFile(lotsFile)
|
|
|
|
for idx, obj in enumerate(lotsData[TTCodeRedemptionDBConsts.LotsFieldName]):
|
|
if obj[TTCodeRedemptionDBConsts.NameFieldName] == lotName:
|
|
lotsData[TTCodeRedemptionDBConsts.LotsFieldName].pop(idx)
|
|
|
|
self.saveFile(lotsFile, lotsData)
|
|
|
|
self._refreshCode2lotName()
|
|
|
|
def getLotNames(self):
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
lotNames = []
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
lotName = lot[TTCodeRedemptionDBConsts.NameFieldName]
|
|
if not self._testing:
|
|
if TTCodeRedemptionDBConsts.TestLotName in lotName:
|
|
continue
|
|
lotNames.append(lotName)
|
|
|
|
return lotNames
|
|
|
|
def getAutoLotNames(self):
|
|
"""
|
|
returns names of all code lots that were automatically generated
|
|
"""
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
|
|
autoLotNames = []
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.ManualFieldName] == False:
|
|
lotName = lot[TTCodeRedemptionDBConsts.NameFieldName]
|
|
|
|
if not self._testing:
|
|
if TTCodeRedemptionDBConsts.TestLotName in lotName:
|
|
continue
|
|
|
|
autoLotNames.append(lotName)
|
|
|
|
return autoLotNames
|
|
|
|
def getManualLotNames(self):
|
|
"""
|
|
returns names of all code lots that were manually generated
|
|
"""
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
|
|
manualLotNames = []
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.ManualFieldName] == True:
|
|
lotName = lot[TTCodeRedemptionDBConsts.NameFieldName]
|
|
|
|
if not self._testing:
|
|
if TTCodeRedemptionDBConsts.TestLotName in lotName:
|
|
continue
|
|
|
|
manualLotNames.append(lotName)
|
|
|
|
return manualLotNames
|
|
|
|
def getExpirationLotNames(self):
|
|
"""
|
|
returns names of all code lots that have expiration dates
|
|
"""
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
|
|
lotNames = []
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.ExpirationFieldName] != '':
|
|
lotName = lot[TTCodeRedemptionDBConsts.NameFieldName]
|
|
|
|
if not self._testing:
|
|
if TTCodeRedemptionDBConsts.TestLotName in lotName:
|
|
continue
|
|
|
|
lotNames.append(lotName)
|
|
|
|
return lotNames
|
|
|
|
def getCodesInLot(self, lotName, justCode=True, filter=None):
|
|
# if justCode, returns list of codes
|
|
# if not justCode, returns list of dict of field->value
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
|
|
if filter is None:
|
|
filter = self.LotFilter.All
|
|
|
|
# TODO: FILTERING
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
if justCode:
|
|
codes = []
|
|
|
|
for codeSet in codeSetData:
|
|
code = str(codeSet[TTCodeRedemptionDBConsts.CodeFieldName])
|
|
codes.append(code)
|
|
|
|
result = codes
|
|
else:
|
|
for codeSet in codeSetData:
|
|
codeSet[TTCodeRedemptionDBConsts.CodeFieldName] = str(codeSet[TTCodeRedemptionDBConsts.CodeFieldName])
|
|
|
|
result = codeSetData
|
|
|
|
return result
|
|
|
|
def _clearCaches(self):
|
|
self._code2lotNameCache.clear()
|
|
self._lotName2manualCache.clear()
|
|
self._code2rewardCache.clear()
|
|
|
|
def _cacheClearTask(self, task):
|
|
self._clearCaches()
|
|
return Task.again
|
|
|
|
def commitOutstandingRedemptions(self):
|
|
if len(self._manualCode2outstandingRedemptions):
|
|
self.notify.info('committing cached manual code redemption counts to DB')
|
|
|
|
for key in self._manualCode2outstandingRedemptions.keys():
|
|
code, lotName = key
|
|
count = self._manualCode2outstandingRedemptions[key]
|
|
self._updateRedemptionCount(code, True, None, lotName, count)
|
|
|
|
self._manualCode2outstandingRedemptions = {}
|
|
|
|
def _updateRedemptionsTask(self, task):
|
|
try:
|
|
self.commitOutstandingRedemptions()
|
|
except TryAgainLater as e:
|
|
pass
|
|
return Task.again
|
|
|
|
def getLotNameFromCode(self, code):
|
|
assert self.notify.debugCall()
|
|
|
|
code = TTCodeDict.getFromReadableCode(code)
|
|
assert TTCodeDict.isLegalCode(code)
|
|
|
|
if self.CacheAllCodes:
|
|
return self._code2lotName.get(code, None)
|
|
|
|
cachedLotName = self._code2lotNameCache.getInfo(code)
|
|
if cachedLotName is not self._code2lotNameCache.NotFound:
|
|
return cachedLotName
|
|
|
|
assert self.notify.debug('lotNameFromCode CACHE MISS (%s)' % (code))
|
|
|
|
self._doCleanup()
|
|
|
|
lotNames = self.getLotNames()
|
|
result = None
|
|
|
|
for lotName in lotNames:
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == code:
|
|
result = lotName
|
|
break
|
|
|
|
if result is not None:
|
|
self._code2lotNameCache.cacheInfo(code, result)
|
|
|
|
return result
|
|
|
|
def getRewardFromCode(self, code):
|
|
assert self.notify.debugCall()
|
|
|
|
code = TTCodeDict.getFromReadableCode(code)
|
|
assert TTCodeDict.isLegalCode(code)
|
|
|
|
lotName = self.getLotNameFromCode(code)
|
|
assert lotName is not None
|
|
|
|
cachedReward = self._code2rewardCache.getInfo(code)
|
|
if cachedReward is not self._code2rewardCache.NotFound:
|
|
return cachedReward
|
|
|
|
assert self.notify.debug('reward from code CACHE MISS (%s)' % (code))
|
|
|
|
self._doCleanup()
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
lotId = -1
|
|
rows = []
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == code:
|
|
lotId = codeSet[TTCodeRedemptionDBConsts.LotIdFieldName]
|
|
break
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.LotIdFieldName] == lotId:
|
|
rows.append(lot)
|
|
|
|
assert len(rows) == 1
|
|
reward = (int(rows[0][TTCodeRedemptionDBConsts.RewardTypeFieldName]), int(rows[0][TTCodeRedemptionDBConsts.RewardItemIdFieldName]))
|
|
|
|
self._code2rewardCache.cacheInfo(code, reward)
|
|
|
|
return reward
|
|
|
|
def lotExists(self, lotName):
|
|
return lotName in self.getLotNames()
|
|
|
|
def codeExists(self, code):
|
|
return self.getLotNameFromCode(code) != None
|
|
|
|
def getRedemptions(self, code):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
|
|
code = TTCodeDict.getFromReadableCode(code)
|
|
|
|
lotName = self.getLotNameFromCode(code)
|
|
|
|
if lotName is None:
|
|
self.notify.error('getRedemptions: could not find code %s' % (code))
|
|
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == code:
|
|
return codeSet[TTCodeRedemptionDBConsts.RedemptionsFieldName]
|
|
|
|
return 0
|
|
|
|
def redeemCode(self, code, avId, rewarder, callback):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
# callback takes a RedeemError
|
|
# 'code' can come from a client, treat with care
|
|
origCode = code
|
|
code = TTCodeDict.getFromReadableCode(code)
|
|
assert TTCodeDict.isLegalCode(code)
|
|
|
|
lotName = self.getLotNameFromCode(code)
|
|
if lotName is None:
|
|
self.air.writeServerEvent('invalidCodeRedemption', avId, '%s' % ((origCode), ))
|
|
callback(TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)
|
|
return
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
cachedManual = self._lotName2manualCache.getInfo(lotName)
|
|
if cachedManual is not self._lotName2manualCache.NotFound:
|
|
manualCode = cachedManual
|
|
else:
|
|
assert self.notify.debug('manualFromCode CACHE MISS (%s)' % (code))
|
|
rows = []
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.NameFieldName] == lotName:
|
|
rows.append(lot)
|
|
|
|
assert len(rows) == 1
|
|
|
|
manualCode = (rows[0][TTCodeRedemptionDBConsts.ManualFieldName] == True)
|
|
|
|
self._lotName2manualCache.cacheInfo(lotName, manualCode)
|
|
|
|
if not manualCode:
|
|
rows = []
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == code:
|
|
codeSet = codeSet
|
|
break
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.LotIdFieldName] == codeSet[TTCodeRedemptionDBConsts.LotIdFieldName] and (lot[TTCodeRedemptionDBConsts.ExpirationFieldName] == '' or datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S') <= lot[TTCodeRedemptionDBConsts.ExpirationFieldName]):
|
|
rows.append(codeSet)
|
|
|
|
assert len(rows) <= 1
|
|
|
|
if not manualCode:
|
|
if len(rows) == 0:
|
|
# code is expired
|
|
callback(TTCodeRedemptionConsts.RedeemErrors.CodeIsExpired, 0)
|
|
return
|
|
|
|
redemptions = rows[0][TTCodeRedemptionDBConsts.RedemptionsFieldName]
|
|
|
|
if redemptions > 0:
|
|
callback(TTCodeRedemptionConsts.RedeemErrors.CodeAlreadyRedeemed, 0)
|
|
return
|
|
|
|
rewardTypeId, rewardItemId = self.getRewardFromCode(code)
|
|
|
|
rewarder._giveReward(avId, rewardTypeId, rewardItemId, Functor(
|
|
self._handleRewardResult, code, manualCode, avId, lotName, rewardTypeId, rewardItemId,
|
|
callback))
|
|
|
|
def _updateRedemptionCount(self, code, manualCode, avId, lotName, count):
|
|
assert self.notify.debugCall()
|
|
|
|
codeSetFile = self.getFileName(self.codeSetFileName % (lotName))
|
|
codeSetData = self.loadCodeSetFile(codeSetFile)
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == code:
|
|
codeSet[TTCodeRedemptionDBConsts.RedemptionsFieldName] = codeSet[TTCodeRedemptionDBConsts.RedemptionsFieldName] + count
|
|
|
|
if not manualCode:
|
|
codeSet[TTCodeRedemptionDBConsts.AvatarIdFieldName] = avId
|
|
|
|
self.saveFile(codeSetFile, codeSetData)
|
|
|
|
def _handleRewardResult(self, code, manualCode, avId, lotName, rewardTypeId, rewardItemId, callback, result):
|
|
assert self.notify.debugCall()
|
|
self._doCleanup()
|
|
|
|
assert TTCodeDict.isLegalCode(code)
|
|
|
|
awardMgrResult = result
|
|
|
|
if awardMgrResult:
|
|
callback(TTCodeRedemptionConsts.RedeemErrors.AwardCouldntBeGiven, awardMgrResult)
|
|
return
|
|
|
|
if manualCode:
|
|
# queue up redemption count for manual code and write every N minutes
|
|
key = (code, lotName)
|
|
self._manualCode2outstandingRedemptions.setdefault(key, 0)
|
|
self._manualCode2outstandingRedemptions[key] += 1
|
|
else:
|
|
self._updateRedemptionCount(code, manualCode, avId, lotName, 1)
|
|
|
|
if not self._testing:
|
|
self.air.writeServerEvent('codeRedeemed', avId, '%s|%s|%s|%s' % (
|
|
(choice(manualCode, code, TTCodeDict.getReadableCode(code))),
|
|
lotName, rewardTypeId, rewardItemId, ))
|
|
|
|
callback(TTCodeRedemptionConsts.RedeemErrors.Success, awardMgrResult)
|
|
|
|
def lookupCodesRedeemedByAvId(self, avId):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
|
|
codes = []
|
|
|
|
# manual lots don't record redeemer avIds since they are single-code-multi-toon
|
|
for lotName in self.getAutoLotNames():
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet.get(TTCodeRedemptionDBConsts.AvatarIdFieldName):
|
|
if codeSet[TTCodeRedemptionDBConsts.AvatarIdFieldName] == avId:
|
|
code = str(codeSet[TTCodeRedemptionDBConsts.CodeFieldName])
|
|
codes.append(code)
|
|
|
|
return codes
|
|
|
|
def getExpiration(self, lotName):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
expiration = ''
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.NameFieldName] == lotName:
|
|
expiration = str(datetime.datetime.strptime(lot[TTCodeRedemptionDBConsts.ExpirationFieldName], '%Y-%m-%d %H:%M:%S').date())
|
|
|
|
return expiration
|
|
|
|
def setExpiration(self, lotName, expiration):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
|
|
lotsFile = self.getFileName(self.lotsFileName)
|
|
lotsData = self.loadLotsFile(lotsFile)
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.NameFieldName] == lotName:
|
|
lot[TTCodeRedemptionDBConsts.ExpirationFieldName] = self._getExpirationString(expiration)
|
|
|
|
self.saveFile(lotsFile, lotsData)
|
|
|
|
def getCodeDetails(self, code):
|
|
assert self.notify.debugCall()
|
|
|
|
self._doCleanup()
|
|
|
|
lotsData = self.loadLotsFile(self.getFileName(self.lotsFileName))
|
|
|
|
for lotName in self.getLotNames():
|
|
codeSetData = self.loadCodeSetFile(self.getFileName(self.codeSetFileName % (lotName)))
|
|
|
|
for codeSet in codeSetData:
|
|
if codeSet[TTCodeRedemptionDBConsts.CodeFieldName] == TTCodeDict.getFromReadableCode(code):
|
|
codeSet[TTCodeRedemptionDBConsts.CodeFieldName] = str(codeSet[TTCodeRedemptionDBConsts.CodeFieldName])
|
|
codeSet[TTCodeRedemptionDBConsts.RewardTypeFieldName] = 0
|
|
codeSet[TTCodeRedemptionDBConsts.RewardItemIdFieldName] = 0
|
|
|
|
for lot in lotsData[TTCodeRedemptionDBConsts.LotsFieldName]:
|
|
if lot[TTCodeRedemptionDBConsts.LotIdFieldName] == codeSet[TTCodeRedemptionDBConsts.LotIdFieldName]:
|
|
codeSet[TTCodeRedemptionDBConsts.RewardTypeFieldName] = lot[TTCodeRedemptionDBConsts.RewardTypeFieldName]
|
|
codeSet[TTCodeRedemptionDBConsts.RewardItemIdFieldName] = lot[TTCodeRedemptionDBConsts.RewardItemIdFieldName]
|
|
|
|
return codeSet
|
|
self.notify.error('code \'%s\' not found' % (code))
|
|
|
|
if __debug__:
|
|
def runTests(self):
|
|
self._doRunTests(self._initializedSV.get())
|
|
self._runTestsFC = FunctionCall(self._doRunTests, self._initializedSV)
|
|
|
|
def _doRunTests(self, initialized):
|
|
if initialized and self.DoSelfTest:
|
|
jobMgr.add(TTCodeRedemptionDBTester(self))
|
|
|
|
# Custom for JSONs
|
|
def loadLotsFile(self, fileName):
|
|
data = {TTCodeRedemptionDBConsts.LotsFieldName: [], TTCodeRedemptionDBConsts.NextLotIdFieldName: 0}
|
|
|
|
try:
|
|
with open(self.filePath + fileName, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
fileExists = True
|
|
except:
|
|
fileExists = False
|
|
|
|
if not fileExists:
|
|
# Use self.update() to setup initial db:
|
|
self.saveFile(fileName, data)
|
|
|
|
return data
|
|
|
|
def loadCodeSpaceFile(self, fileName):
|
|
data = {TTCodeRedemptionDBConsts.CodeLengthFieldName: 4, TTCodeRedemptionDBConsts.NextCodeValueFieldName: 0}
|
|
|
|
try:
|
|
with open(self.filePath + fileName, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
fileExists = True
|
|
except:
|
|
fileExists = False
|
|
|
|
if not fileExists:
|
|
# Use self.update() to setup initial db:
|
|
self.saveFile(fileName, data)
|
|
|
|
return data
|
|
|
|
def loadCodeSetFile(self, fileName):
|
|
data = []
|
|
|
|
try:
|
|
with open(self.filePath + fileName, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
fileExists = True
|
|
except:
|
|
fileExists = False
|
|
|
|
return data
|
|
|
|
def saveFile(self, fileName, jsonData):
|
|
if not os.path.exists(self.filePath):
|
|
os.makedirs(self.filePath)
|
|
|
|
with open(self.filePath + fileName, 'w+') as f:
|
|
json.dump(jsonData, f, indent=4)
|
|
|
|
def deleteFile(self, fileName):
|
|
if os.path.exists(self.filePath + fileName):
|
|
os.remove(self.filePath + fileName)
|
|
|
|
def getFileName(self, fileName):
|
|
return '%s.json' % (fileName)
|