Store variables in a shared database. Condition to retreive the last n variables of a specific name. Fix #61
This commit is contained in:
parent
2eeee9095f
commit
8227cceffb
4 changed files with 115 additions and 3 deletions
|
@ -26,6 +26,7 @@ from hugvey.speech.recorder import Recorder
|
||||||
from pythonosc import udp_client, osc_server, dispatcher
|
from pythonosc import udp_client, osc_server, dispatcher
|
||||||
import copy
|
import copy
|
||||||
from pythonosc.osc_server import AsyncIOOSCUDPServer
|
from pythonosc.osc_server import AsyncIOOSCUDPServer
|
||||||
|
from hugvey.variablestore import VariableStore
|
||||||
|
|
||||||
mainLogger = logging.getLogger("hugvey")
|
mainLogger = logging.getLogger("hugvey")
|
||||||
|
|
||||||
|
@ -93,6 +94,11 @@ class CentralCommand(object):
|
||||||
|
|
||||||
voice_dir = os.path.join(self.config['web']['files_dir'], 'voices')
|
voice_dir = os.path.join(self.config['web']['files_dir'], 'voices')
|
||||||
self.voiceStorage = VoiceStorage(voice_dir, self.languageConfig)
|
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)
|
self.panopticon = Panopticon(self, self.config, self.voiceStorage)
|
||||||
|
|
||||||
|
@ -419,6 +425,8 @@ class CentralCommand(object):
|
||||||
self.catchException(self.oscListener()))
|
self.catchException(self.oscListener()))
|
||||||
self.tasks['redLightController'] = self.loop.create_task(
|
self.tasks['redLightController'] = self.loop.create_task(
|
||||||
self.catchException(self.redLightController()))
|
self.catchException(self.redLightController()))
|
||||||
|
self.tasks['variableStore'] = self.loop.create_task(
|
||||||
|
self.catchException(self.variableStore.queueProcessor()))
|
||||||
|
|
||||||
for hid in self.hugvey_ids:
|
for hid in self.hugvey_ids:
|
||||||
self.tasks['voiceListener'] = self.loop.create_task(
|
self.tasks['voiceListener'] = self.loop.create_task(
|
||||||
|
|
|
@ -143,6 +143,9 @@ class Message(object):
|
||||||
# asyncio.get_event_loop().call_soon_threadsafe(self.getAudioFilePath)
|
# asyncio.get_event_loop().call_soon_threadsafe(self.getAudioFilePath)
|
||||||
self.logger.warn(f"started {name}")
|
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):
|
def getText(self):
|
||||||
# sort reverse to avoid replacing the wrong variable
|
# sort reverse to avoid replacing the wrong variable
|
||||||
self.variables.sort(key=len, reverse=True)
|
self.variables.sort(key=len, reverse=True)
|
||||||
|
@ -150,7 +153,7 @@ class Message(object):
|
||||||
# self.logger.debug(f"Getting text for {self.id}")
|
# self.logger.debug(f"Getting text for {self.id}")
|
||||||
for var in self.variables:
|
for var in self.variables:
|
||||||
self.logger.debug(f"try replacing ${var} with {self.variableValues[var]} in {text}")
|
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)
|
text = text.replace('$'+var, replacement)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
@ -330,6 +333,9 @@ class Condition(object):
|
||||||
condition.method = condition._hasPlayed
|
condition.method = condition._hasPlayed
|
||||||
if data['type'] == "variableEquals":
|
if data['type'] == "variableEquals":
|
||||||
condition.method = condition._variableEquals
|
condition.method = condition._variableEquals
|
||||||
|
if data['type'] == "variable_storage":
|
||||||
|
condition.method = condition._hasVariableStorage
|
||||||
|
condition.hasRan = False
|
||||||
|
|
||||||
if 'vars' in data:
|
if 'vars' in data:
|
||||||
condition.vars = data['vars']
|
condition.vars = data['vars']
|
||||||
|
@ -451,6 +457,32 @@ class Condition(object):
|
||||||
|
|
||||||
return r
|
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:
|
def _hasMetReplyContains(self, story) -> bool:
|
||||||
"""
|
"""
|
||||||
Check the reply for specific characteristics:
|
Check the reply for specific characteristics:
|
||||||
|
@ -1237,13 +1269,15 @@ class Story(object):
|
||||||
else:
|
else:
|
||||||
self.variables[variableName].append(message)
|
self.variables[variableName].append(message)
|
||||||
|
|
||||||
def setVariableValue(self, name, value):
|
def setVariableValue(self, name, value, store=True):
|
||||||
if name not in self.variables:
|
if name not in self.variables:
|
||||||
self.logger.warn(f"Set variable that is not needed in the story: {name}")
|
self.logger.warn(f"Set variable that is not needed in the story: {name}")
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Set variable {name} to {value}")
|
self.logger.debug(f"Set variable {name} to {value}")
|
||||||
|
|
||||||
self.variableValues[name] = value
|
self.variableValues[name] = value
|
||||||
|
if store:
|
||||||
|
self.hugvey.command.variableStore.addVariable(name, value, self.hugvey.id)
|
||||||
|
|
||||||
if name not in self.variables:
|
if name not in self.variables:
|
||||||
return
|
return
|
||||||
|
|
61
hugvey/variablestore.py
Normal file
61
hugvey/variablestore.py
Normal 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
|
|
@ -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." },
|
'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." },
|
'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': {
|
'replyContains': {
|
||||||
'delays.0.minReplyDuration': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 1 - reply duration', 'unit': "s", 'readonly': 'readonly' },
|
'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" },
|
'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'] : "" )
|
// crel('span', {'class': 'label-unit'}, attr.hasOwnProperty('unit') ? attr['unit'] : "" )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
if(attr.hasOwnProperty('description')) {
|
||||||
|
inputs.push(crel('div', {'class':'description'}, attr['description']));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return inputs;
|
return inputs;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue