Store variables in a shared database. Condition to retreive the last n variables of a specific name. Fix #61

This commit is contained in:
Ruben van de Ven 2019-07-04 22:05:51 +02:00
parent 2eeee9095f
commit 8227cceffb
4 changed files with 115 additions and 3 deletions

View File

@ -26,6 +26,7 @@ from hugvey.speech.recorder import Recorder
from pythonosc import udp_client, osc_server, dispatcher
import copy
from pythonosc.osc_server import AsyncIOOSCUDPServer
from hugvey.variablestore import VariableStore
mainLogger = logging.getLogger("hugvey")
@ -93,6 +94,11 @@ class CentralCommand(object):
voice_dir = os.path.join(self.config['web']['files_dir'], 'voices')
self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig)
varDb = os.path.join(
self.config['voice']['record_dir'],
'hugvey_variable_store.db'
)
self.variableStore = VariableStore(varDb)
self.panopticon = Panopticon(self, self.config, self.voiceStorage)
@ -419,6 +425,8 @@ class CentralCommand(object):
self.catchException(self.oscListener()))
self.tasks['redLightController'] = self.loop.create_task(
self.catchException(self.redLightController()))
self.tasks['variableStore'] = self.loop.create_task(
self.catchException(self.variableStore.queueProcessor()))
for hid in self.hugvey_ids:
self.tasks['voiceListener'] = self.loop.create_task(

View File

@ -142,6 +142,9 @@ class Message(object):
asyncio.get_event_loop().create_task(self.getAudioFilePath())
# asyncio.get_event_loop().call_soon_threadsafe(self.getAudioFilePath)
self.logger.warn(f"started {name}")
def getVariableValue(self, var):
return self.variableValues[var] if (self.variableValues[var] is not None) else self.story.configuration.nothing_text #TODO: translate nothing to each language
def getText(self):
# sort reverse to avoid replacing the wrong variable
@ -150,7 +153,7 @@ class Message(object):
# self.logger.debug(f"Getting text for {self.id}")
for var in self.variables:
self.logger.debug(f"try replacing ${var} with {self.variableValues[var]} in {text}")
replacement = self.variableValues[var] if (self.variableValues[var] is not None) else self.story.configuration.nothing_text #TODO: translate nothing to each language
replacement = self.getVariableValue(var)
text = text.replace('$'+var, replacement)
return text
@ -330,6 +333,9 @@ class Condition(object):
condition.method = condition._hasPlayed
if data['type'] == "variableEquals":
condition.method = condition._variableEquals
if data['type'] == "variable_storage":
condition.method = condition._hasVariableStorage
condition.hasRan = False
if 'vars' in data:
condition.vars = data['vars']
@ -450,6 +456,32 @@ class Condition(object):
)
return r
def _hasVariableStorage(self, story) -> bool:
if not story.lastMsgFinishTime:
return False
if self.hasRan:
# Prevent multiple runs of the same query within eg. waiting for a timeout.
return False
number = int(self.vars['number'])
varValues = story.hugvey.command.variableStore.getLastOfName(self.vars['var_name'], number)
self.hasRan = True
if len(varValues) < number:
story.logger.info(f"{self.id}: Too few instances of {self.vars['var_name']}")
return False
for i in range(number):
story.setVariableValue(
f"stored_{self.vars['var_name']}_{i+1}",
varValues[i],
store=False
)
return True
def _hasMetReplyContains(self, story) -> bool:
"""
@ -1237,13 +1269,15 @@ class Story(object):
else:
self.variables[variableName].append(message)
def setVariableValue(self, name, value):
def setVariableValue(self, name, value, store=True):
if name not in self.variables:
self.logger.warn(f"Set variable that is not needed in the story: {name}")
else:
self.logger.debug(f"Set variable {name} to {value}")
self.variableValues[name] = value
if store:
self.hugvey.command.variableStore.addVariable(name, value, self.hugvey.id)
if name not in self.variables:
return

61
hugvey/variablestore.py Normal file
View File

@ -0,0 +1,61 @@
import sqlite3
import asyncio
import logging
mainLogger = logging.getLogger("hugvey")
logger = mainLogger.getChild("variableStore")
class Variable:
def __init__(self, name: str, value: str, hugveyId: int):
self.name = name
self.value = value
self.hugveyId = hugveyId
class VariableStore:
def __init__(self, db_filename):
self.conn = sqlite3.connect(db_filename, check_same_thread=False)
# make sure the table exits.
createSqls = ["""
CREATE TABLE IF NOT EXISTS `variables` (
`name` VARCHAR(255),
`hugvey` INTEGER,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`val` VARCHAR(1024)
);
""",
"""
CREATE INDEX IF NOT EXISTS `name_time` ON `variables` (
`name`,
`createdAt` DESC
);
"""]
cur = self.conn.cursor()
for sql in createSqls:
cur.execute(sql)
self.conn.commit()
self.q = asyncio.Queue()
def addVariable(self, name, value, hugveyId):
logger.debug(f"Queing storing of {name} for {hugveyId}")
self.q.put_nowait(Variable(name, value, hugveyId))
async def queueProcessor(self):
while True:
#: :var v: Variable
v = await self.q.get()
c = self.conn.cursor()
logger.info(f"Store variable {v.name} for {v.hugveyId}: '{v.value}'")
c.execute("INSERT INTO variables (name, hugvey, createdAt, val) VALUES (?,?, current_timestamp,?)", (v.name, v.hugveyId, v.value))
self.conn.commit()
c.close()
def getLastOfName(self, name, n = 10):
cur = self.conn.cursor()
logging.debug(f"Get last {n} stored variables of {name}")
cur.execute("SELECT val FROM variables WHERE name = ? ORDER BY createdAt DESC LIMIT ?", (name, n))
values = [v[0] for v in cur.fetchall()]
cur.close()
return values

View File

@ -1607,6 +1607,11 @@ class Graph {
'onlyIfNoReply': { 'type': 'checkbox', label: "Only if no reply", "title": "This timeout is only used if the participant doesn't say a word. If the participant starts speaking within the time of this timeout condition, only other conditions are applicable." },
'needsReply': { 'type': 'checkbox', label: "Reply needed", "title": "If checked, the timeout is counted if met. Used by consecutive-timeouts diversions." },
},
'variable_storage': {
// when matched, variable will be accessible as {store_name_1}
'var_name': { 'label': "Variable name", 'type': 'text', 'description': "When matched, variable will be accessible as $stored_VARNAME_1, $stored_VARNAME_2.. etc (use the name given here instead of VARNAME)" },
'number': { 'label': "Nr. of items to get", 'type': 'number', 'value': 5, 'min': 0, 'step': 1 },
},
'replyContains': {
'delays.0.minReplyDuration': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 1 - reply duration', 'unit': "s", 'readonly': 'readonly' },
'delays.0.waitTime': { 'type': 'number', 'value': 3, 'min': 0, 'step': 0.1 , 'label': 'Delay 1 - wait time', 'unit': "s" },
@ -1668,6 +1673,10 @@ class Graph {
// crel('span', {'class': 'label-unit'}, attr.hasOwnProperty('unit') ? attr['unit'] : "" )
)
);
if(attr.hasOwnProperty('description')) {
inputs.push(crel('div', {'class':'description'}, attr['description']));
}
}
return inputs;
}