Merge branch 'dev' into feature/display_state_manager
commit
eb03c3592c
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#Docker doesn't use sudo so need one without sudo for automation of image.
|
||||||
|
|
||||||
|
apt-get install -y \
|
||||||
|
git \
|
||||||
|
python \
|
||||||
|
python-dev \
|
||||||
|
python-setuptools \
|
||||||
|
python-virtualenv \
|
||||||
|
python-gobject-dev \
|
||||||
|
virtualenvwrapper \
|
||||||
|
libtool \
|
||||||
|
libffi-dev \
|
||||||
|
libssl-dev \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
bison \
|
||||||
|
swig \
|
||||||
|
libglib2.0-dev \
|
||||||
|
s3cmd \
|
||||||
|
portaudio19-dev \
|
||||||
|
mpg123 \
|
||||||
|
screen \
|
||||||
|
flac \
|
||||||
|
curl \
|
||||||
|
libicu-dev \
|
||||||
|
pkg-config \
|
||||||
|
automake
|
27
dev_setup.sh
27
dev_setup.sh
|
@ -41,28 +41,33 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# create virtualenv, consistent with virtualenv-wrapper conventions
|
# create virtualenv, consistent with virtualenv-wrapper conventions
|
||||||
if [ ! -d ${VIRTUALENV_ROOT} ]; then
|
if [ ! -d "${VIRTUALENV_ROOT}" ]; then
|
||||||
mkdir -p $(dirname ${VIRTUALENV_ROOT})
|
mkdir -p $(dirname "${VIRTUALENV_ROOT}")
|
||||||
virtualenv -p python2.7 ${VIRTUALENV_ROOT}
|
virtualenv -p python2.7 "${VIRTUALENV_ROOT}"
|
||||||
fi
|
fi
|
||||||
source ${VIRTUALENV_ROOT}/bin/activate
|
source "${VIRTUALENV_ROOT}/bin/activate"
|
||||||
cd ${TOP}
|
cd "${TOP}"
|
||||||
easy_install pip==7.1.2 # force version of pip
|
easy_install pip==7.1.2 # force version of pip
|
||||||
pip install --upgrade virtualenv
|
pip install --upgrade virtualenv
|
||||||
|
|
||||||
# install requirements (except pocketsphinx)
|
# install requirements (except pocketsphinx)
|
||||||
pip2 install -r requirements.txt
|
pip2 install -r requirements.txt
|
||||||
|
|
||||||
CORES=$(nproc)
|
if [[ $(free|awk '/^Mem:/{print $2}') -lt 1572864 ]] ; then
|
||||||
echo Building with $CORES cores.
|
CORES=1
|
||||||
|
else
|
||||||
|
CORES=$(nproc)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building with $CORES cores."
|
||||||
|
|
||||||
#build and install pocketsphinx
|
#build and install pocketsphinx
|
||||||
#cd ${TOP}
|
#cd ${TOP}
|
||||||
#${TOP}/scripts/install-pocketsphinx.sh -q
|
#${TOP}/scripts/install-pocketsphinx.sh -q
|
||||||
#build and install mimic
|
#build and install mimic
|
||||||
cd ${TOP}
|
cd "${TOP}"
|
||||||
${TOP}/scripts/install-mimic.sh
|
echo "WARNING: The following can take a long time to run!"
|
||||||
|
"${TOP}/scripts/install-mimic.sh"
|
||||||
|
|
||||||
# install pygtk for desktop_launcher skill
|
# install pygtk for desktop_launcher skill
|
||||||
${TOP}/scripts/install-pygtk.sh
|
"${TOP}/scripts/install-pygtk.sh"
|
||||||
|
|
||||||
|
|
222
msm/msm
222
msm/msm
|
@ -23,10 +23,30 @@
|
||||||
# This script assists in the installation and management of
|
# This script assists in the installation and management of
|
||||||
# skills loaded from Github.
|
# skills loaded from Github.
|
||||||
|
|
||||||
set -e
|
mycroft_skill_folder=${mycroft_skill_folder:-"/opt/mycroft/skills"}
|
||||||
|
if [[ ! -d "${mycroft_skill_folder}" ]] ; then
|
||||||
|
echo "Unable to access ${mycroft_skill_folder}!"
|
||||||
|
exit 101
|
||||||
|
fi
|
||||||
|
|
||||||
mycroft_skill_folder="/opt/mycroft/skills"
|
# picroft?
|
||||||
mycroft_virtualenv=~/.virtualenvs/mycroft/bin/activate
|
if [[ "$(hostname)" == 'picroft' ]] || [[ -x /home/pi/bin/cli ]] ; then
|
||||||
|
picroft='true'
|
||||||
|
else
|
||||||
|
picroft='false'
|
||||||
|
if [[ -r /etc/bash_completion.d/virtualenvwrapper ]]; then
|
||||||
|
source /etc/bash_completion.d/virtualenvwrapper
|
||||||
|
else
|
||||||
|
if locate virtualenvwrapper ; then
|
||||||
|
if ! source $(locate virtualenvwrapper) ; then
|
||||||
|
echo "Unable to locate virtualenvwrapper.sh, will not be able to install skills!"
|
||||||
|
vwrap='false'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
default_skills="skill-alarm skill-audio-record skill-configuration skill-date-time skill-desktop-launcher skill-ip skill-joke skill-hello-world skill-media skill-npr-news skill-naptime skill-pairing skill-personal skill-reminder skill-installer skill-singing skill-speak skill-spelling skill-stop skill-stock skill-volume skill-weather skill-wiki skill-wolfram-alpha skill-mark1-demo"
|
||||||
|
|
||||||
echo "####### Mycroft Skill Manager #######"
|
echo "####### Mycroft Skill Manager #######"
|
||||||
|
|
||||||
|
@ -34,103 +54,157 @@ function help() {
|
||||||
echo "msm: Mycroft Skill Manager"
|
echo "msm: Mycroft Skill Manager"
|
||||||
echo -e " Copyright (c) 2017 Mycroft AI, Inc. All rights reserved.\n"
|
echo -e " Copyright (c) 2017 Mycroft AI, Inc. All rights reserved.\n"
|
||||||
echo "usage: msm install <repository> or <name>"
|
echo "usage: msm install <repository> or <name>"
|
||||||
echo " Installs the given Skill into the /opt/mycroft/skills directory"
|
echo " Installs the given Skill into the ${mycroft_skill_folder}"
|
||||||
echo " where <repository> is the address of the skill in Github."
|
echo " where <repository> is the address of the skill in Github."
|
||||||
|
echo "example: msm search rss-skill"
|
||||||
echo -e "example: msm install https://github.com/ethanaward/demo_skill.git\n"
|
echo -e "example: msm install https://github.com/ethanaward/demo_skill.git\n"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function list() {
|
||||||
function goto_skills_dir {
|
if hash curl ; then
|
||||||
if [ ! -d $mycroft_skill_folder ]; then
|
if ! curl -s "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules" ; then
|
||||||
echo "Couldn't install skill, $mycroft_skill_folder does not exist"
|
echo "Unable to pull master skills list!"
|
||||||
exit 1
|
exit 111
|
||||||
fi
|
fi
|
||||||
cd $mycroft_skill_folder
|
else
|
||||||
|
if ! wget -qO- "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules" ; then
|
||||||
|
echo "Unable to pull master skills list!"
|
||||||
|
exit 112
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function install() {
|
function install() {
|
||||||
goto_skills_dir
|
cd "${mycroft_skill_folder}"
|
||||||
if [ -z "$2" ]; then
|
if [[ "${vwrap}" == 'false' ]] ; then
|
||||||
echo "You must pass the git url or skill name"
|
echo "Missing virtualwrapper, cowardly refusing to install skills."
|
||||||
exit 1
|
return 5
|
||||||
fi
|
fi
|
||||||
if [[ "$2" == "git@"* || "$2" == "https://"* || "$2" == "http://"* ]]; then
|
# loop through list of arguments
|
||||||
repo=$2
|
while [[ $# -gt 0 ]] ; do
|
||||||
|
cd "${mycroft_skill_folder}"
|
||||||
|
iskill="${1}";
|
||||||
|
shift;
|
||||||
|
echo "Attempting to install ${iskill}..."
|
||||||
|
if [[ "${iskill}" == "git@"* || "${iskill}" == "https://"* || "${iskill}" == "http://"* ]]; then
|
||||||
|
repo="${iskill}"
|
||||||
else
|
else
|
||||||
skill_list="`curl -s "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules"`"
|
skills=$(list | grep -n 'submodule' | sed 's/[[:space:]]//g' | sed 's/\[submodule"//g' | sed 's/"\]//g')
|
||||||
skills=`echo "$skill_list" | grep -n 'submodule' | sed 's/[[:space:]]//g' | sed 's/\[submodule"//g' | sed 's/"\]//g'`
|
exact_match=$(echo "$skills" | grep -i ".*:${iskill}$")
|
||||||
exact_match=`echo "$skills" | grep -i ".*:$2$"`
|
skill=$(echo "$skills" | grep -i ".*:.*${iskill}.*")
|
||||||
skill=`echo "$skills" | grep -i ".*:.*$2.*"`
|
if [[ ! -z "${exact_match}" ]]; then
|
||||||
if [ ! -z $exact_match ]; then
|
skill=${exact_match}
|
||||||
skill=$exact_match
|
|
||||||
fi
|
fi
|
||||||
git_line=`echo "$skill" | sed 's/\:.*//'`
|
git_line=$(echo "$skill" | sed 's/\:.*//')
|
||||||
|
|
||||||
if [[ $skill == *$'\n'* ]]; then
|
if [[ "${skill}" == *$'\n'* ]]; then
|
||||||
echo -e "Your search has multiple choices\n\n$skill" | sed 's/.*://g'
|
echo -e "Your search has multiple choices\n\n$skill" | sed 's/.*://g'
|
||||||
exit 2
|
return 3
|
||||||
else
|
else
|
||||||
if [ -z $git_line ]; then
|
if [[ -z "${git_line}" ]]; then
|
||||||
echo "Skill not found"
|
echo "A ${iskill} skill was not found"
|
||||||
exit 3
|
return 3
|
||||||
fi
|
fi
|
||||||
repo_line=$(($git_line + 2))
|
repo_line=$(($git_line + 2))
|
||||||
repo=`echo "$skill_list" | sed -n $repo_line'{p;q;}' | sed 's/[[:space:]]//g' | sed 's/url=//g'`
|
repo=$(list | sed -n $repo_line'{p;q;}' | sed 's/[[:space:]]//g' | sed 's/url=//g')
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
git_name=`echo "$repo" | sed 's/.*\///'`
|
git_name=$(echo "${repo}" | sed 's/.*\///')
|
||||||
name=`echo "$git_name" | sed 's/.git//'`
|
name=$(echo "$git_name" | sed 's/.git//')
|
||||||
|
if [[ -d "${mycroft_skill_folder}/${name}" ]] ; then
|
||||||
|
echo "Skill appears to exist already. Perhaps you meant to use update?"
|
||||||
|
continue 169
|
||||||
|
fi
|
||||||
echo "Cloning repository"
|
echo "Cloning repository"
|
||||||
git clone $repo >> /dev/null
|
git clone "${repo}" >> /dev/null
|
||||||
cd $name
|
if ! cd "${name}" ; then
|
||||||
if [ -f "requirements.txt" ]; then
|
echo "Unable to access directory ${name}!"
|
||||||
echo "Installing libraries requirements"
|
return 102
|
||||||
pip install -r requirements.txt
|
|
||||||
fi
|
fi
|
||||||
echo "Skill installed!"
|
if [[ "${picroft}" == "true" ]] ; then
|
||||||
|
if ! sudo chown -R mycroft:mycroft "${mycroft_skill_folder}/${name}" ; then
|
||||||
|
echo "Unable to chown install directory ${name}!"
|
||||||
|
return 123
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -f "requirements.txt" ]]; then
|
||||||
|
echo "Installing libraries requirements"
|
||||||
|
if [[ "${picroft}" == 'false' ]]; then
|
||||||
|
if [[ "${VIRTUAL_ENV}" =~ .mycroft$ ]] ; then
|
||||||
|
if ! pip install -r requirements.txt ; then
|
||||||
|
echo "Unable to install requirements for skill ${iskill}!"
|
||||||
|
return 121
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if workon mycroft ; then
|
||||||
|
if ! pip install -r requirements.txt ; then
|
||||||
|
echo "Unable to install requirements for skill ${iskill}!"
|
||||||
|
deactivate mycroft
|
||||||
|
return 121
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Unable to activate mycroft virtualenv!"
|
||||||
|
deactivate
|
||||||
|
return 120
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! sudo pip install -r requirements.txt ; then
|
||||||
|
echo "Unable to install requirements for skill ${iskill}!"
|
||||||
|
return 121
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "The ${iskill} skill has been installed!"
|
||||||
|
done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
goto_skills_dir
|
cd "${mycroft_skill_folder}"
|
||||||
for d in $(ls -d */); do
|
for d in $(find "${mycroft_skill_folder}" -mindepth 1 -maxdepth 1 -type d |grep -v '.git'$ ); do
|
||||||
if git -C "$d" rev-parse --git-dir > /dev/null 2>&1; then
|
if git -C "$d" rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
cd $d
|
cd "${d}"
|
||||||
if [[ -z $(git status --porcelain) ]]; then
|
if [[ -z $(git status --porcelain) ]]; then
|
||||||
git fetch
|
git fetch
|
||||||
git reset --hard origin/master
|
git reset --hard origin/master
|
||||||
fi
|
fi
|
||||||
cd ..
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
function install_defaults() {
|
|
||||||
skills=( "alarm" "audio-record" "configuration" "date-time" "desktop-launcher" "ip" "joke" "hello-world" "media" "npr-news" "naptime" "pairing" "personal" "reminder" "installer" "singing" "speak" "spelling" "stop" "stock" "volume" "weather" "wiki" "wolfram-alpha" "mark1-demo" )
|
|
||||||
for i in "${skills[@]}"
|
|
||||||
do
|
|
||||||
if [ ! -d "$mycroft_skill_folder/skill-$i" ]; then
|
|
||||||
install "" "https://github.com/MycroftAI/skill-$i.git"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
update
|
|
||||||
echo "Installed!"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function list() {
|
|
||||||
curl -s "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules" | grep 'submodule "' | sed 's/\[submodule "//g'| sed 's/"\]//g'
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "$1" = "install" ]; then
|
|
||||||
install $*
|
|
||||||
elif [ "$1" = "list" ]; then
|
|
||||||
echo -e "Searching...\n"
|
|
||||||
list
|
|
||||||
elif [ "$1" = "update" ]; then
|
|
||||||
update
|
|
||||||
elif [ "$1" = "default" ]; then
|
|
||||||
install_defaults
|
|
||||||
else
|
|
||||||
help
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
search_list=$(list | grep 'submodule "' | sed 's/\[submodule "//g'| sed 's/"\]//g')
|
||||||
|
while [[ $# -gt 0 ]] ; do
|
||||||
|
search_string=$1
|
||||||
|
shift
|
||||||
|
while read -r matches; do
|
||||||
|
if [[ "${search_string}" == "${matches}" ]] ; then
|
||||||
|
echo "Exact match found: ${matches}"
|
||||||
|
else
|
||||||
|
echo "Possible match: ${matches}"
|
||||||
|
fi
|
||||||
|
done < <(grep -i "${search_string}" <<< "${search_list}")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#### Main
|
||||||
|
|
||||||
|
OPT=$1
|
||||||
|
shift
|
||||||
|
case ${OPT} in
|
||||||
|
"install") if [[ $# -gt 0 ]] ; then install $(echo "$*") ; else help ; fi;;
|
||||||
|
"list") list | grep 'submodule "' | sed 's/\[submodule "//g'| sed 's/"\]//g' ;;
|
||||||
|
"update") update ;;
|
||||||
|
"default") install $(echo ${default_skills}) ;;
|
||||||
|
"search") if [[ $# -gt 0 ]] ; then search $(echo "$*") ; else help ; fi;;
|
||||||
|
*) help ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit_code=$?
|
||||||
|
if [[ ${exit_code} -gt 0 ]] ; then
|
||||||
|
echo "Sorry I'm unable to complete the request! Check the error messages above for why. Err ${exit_code}"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
|
|
@ -248,20 +248,20 @@ def rebuild_filtered_log():
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Capturing output from Mycroft
|
# Capturing output from Mycroft
|
||||||
|
|
||||||
def handle_speak(event):
|
tts_threads = []
|
||||||
global chat
|
|
||||||
|
|
||||||
|
def start_tts(utterance):
|
||||||
|
"""
|
||||||
|
Begin speaking in another thread to redirect output
|
||||||
|
Otherwise, the CLI get's polluted with text to speech debug
|
||||||
|
"""
|
||||||
global tts
|
global tts
|
||||||
mutex.acquire()
|
mutex.acquire()
|
||||||
|
|
||||||
if not bQuiet:
|
if not bQuiet:
|
||||||
ws.emit(Message("recognizer_loop:audio_output_start"))
|
ws.emit(Message("recognizer_loop:audio_output_start"))
|
||||||
try:
|
try:
|
||||||
utterance = event.data.get('utterance')
|
|
||||||
if bSimple:
|
|
||||||
print(">> " + utterance)
|
|
||||||
else:
|
|
||||||
chat.append(">> " + utterance)
|
|
||||||
draw_screen()
|
|
||||||
if not bQuiet:
|
|
||||||
if not tts:
|
if not tts:
|
||||||
tts = TTSFactory.create()
|
tts = TTSFactory.create()
|
||||||
tts.init(ws)
|
tts.init(ws)
|
||||||
|
@ -272,6 +272,21 @@ def handle_speak(event):
|
||||||
ws.emit(Message("recognizer_loop:audio_output_end"))
|
ws.emit(Message("recognizer_loop:audio_output_end"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_speak(event):
|
||||||
|
global chat
|
||||||
|
global tts_threads
|
||||||
|
utterance = event.data.get('utterance')
|
||||||
|
if bSimple:
|
||||||
|
print(">> " + utterance)
|
||||||
|
else:
|
||||||
|
chat.append(">> " + utterance)
|
||||||
|
draw_screen()
|
||||||
|
if not bQuiet:
|
||||||
|
t = Thread(start_tts, utterance)
|
||||||
|
t.start()
|
||||||
|
tts_threads.append(t)
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
# Once the websocket has connected, just watch it for speak events
|
# Once the websocket has connected, just watch it for speak events
|
||||||
ws.run_forever()
|
ws.run_forever()
|
||||||
|
@ -749,7 +764,7 @@ def main(stdscr):
|
||||||
# resizeterm() causes another curses.KEY_RESIZE, so
|
# resizeterm() causes another curses.KEY_RESIZE, so
|
||||||
# we need to capture that to prevent a loop of resizes
|
# we need to capture that to prevent a loop of resizes
|
||||||
c = scr.getch()
|
c = scr.getch()
|
||||||
elif c == curses.KEY_BACKSPACE:
|
elif c == curses.KEY_BACKSPACE or c == 127:
|
||||||
# Backspace to erase a character in the utterance
|
# Backspace to erase a character in the utterance
|
||||||
line = line[:-1]
|
line = line[:-1]
|
||||||
elif curses.ascii.isascii(c):
|
elif curses.ascii.isascii(c):
|
||||||
|
|
|
@ -302,7 +302,7 @@ class WiFi:
|
||||||
if event and event.data.get("msg"):
|
if event and event.data.get("msg"):
|
||||||
self.intro_msg = event.data.get("msg")
|
self.intro_msg = event.data.get("msg")
|
||||||
self.allow_timeout = True
|
self.allow_timeout = True
|
||||||
if event and event.data.get("allow_timeout"):
|
if event and event.data.get("allow_timeout") is False:
|
||||||
self.allow_timeout = event.data.get("allow_timeout")
|
self.allow_timeout = event.data.get("allow_timeout")
|
||||||
|
|
||||||
# Fire up our access point
|
# Fire up our access point
|
||||||
|
@ -417,7 +417,7 @@ class WiFi:
|
||||||
# has disconnected
|
# has disconnected
|
||||||
if not self._is_ARP_filled():
|
if not self._is_ARP_filled():
|
||||||
cARPFailures += 1
|
cARPFailures += 1
|
||||||
if cARPFailures > 2:
|
if cARPFailures > 5:
|
||||||
self._connection_prompt("Connection lost.")
|
self._connection_prompt("Connection lost.")
|
||||||
bHasConnected = False
|
bHasConnected = False
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -91,7 +91,6 @@ class ConfigurationLoader(object):
|
||||||
locations = ConfigurationLoader.init_locations(locations,
|
locations = ConfigurationLoader.init_locations(locations,
|
||||||
keep_user_config)
|
keep_user_config)
|
||||||
ConfigurationLoader.validate(config, locations)
|
ConfigurationLoader.validate(config, locations)
|
||||||
|
|
||||||
for location in locations:
|
for location in locations:
|
||||||
config = ConfigurationLoader.__load(config, location)
|
config = ConfigurationLoader.__load(config, location)
|
||||||
|
|
||||||
|
@ -135,6 +134,7 @@ class RemoteConfiguration(object):
|
||||||
config in the [core] config section
|
config in the [core] config section
|
||||||
"""
|
"""
|
||||||
IGNORED_SETTINGS = ["uuid", "@type", "active", "user", "device"]
|
IGNORED_SETTINGS = ["uuid", "@type", "active", "user", "device"]
|
||||||
|
WEB_CONFIG_CACHE = '/opt/mycroft/web_config_cache.json'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(config):
|
def validate(config):
|
||||||
|
@ -156,8 +156,11 @@ class RemoteConfiguration(object):
|
||||||
if location:
|
if location:
|
||||||
setting["location"] = location
|
setting["location"] = location
|
||||||
RemoteConfiguration.__load(config, setting)
|
RemoteConfiguration.__load(config, setting)
|
||||||
|
RemoteConfiguration.__store_cache(setting)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.warn("Failed to fetch remote configuration: %s" % repr(e))
|
LOG.warn("Failed to fetch remote configuration: %s" % repr(e))
|
||||||
|
RemoteConfiguration.__load_cache(config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
LOG.debug("Remote configuration not activated.")
|
LOG.debug("Remote configuration not activated.")
|
||||||
return config
|
return config
|
||||||
|
@ -173,10 +176,35 @@ class RemoteConfiguration(object):
|
||||||
config[key] = config.get(key, {})
|
config[key] = config.get(key, {})
|
||||||
RemoteConfiguration.__load(config[key], v)
|
RemoteConfiguration.__load(config[key], v)
|
||||||
elif isinstance(v, list):
|
elif isinstance(v, list):
|
||||||
|
if key not in config:
|
||||||
|
config[key] = {}
|
||||||
RemoteConfiguration.__load_list(config[key], v)
|
RemoteConfiguration.__load_list(config[key], v)
|
||||||
else:
|
else:
|
||||||
config[key] = v
|
config[key] = v
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __store_cache(setting):
|
||||||
|
"""
|
||||||
|
Cache the received settings locally. The cache will be used if
|
||||||
|
the remote is unreachable to load settings that are as close
|
||||||
|
to the user's as possible
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
# Remove server specific entries
|
||||||
|
RemoteConfiguration.__load(config, setting)
|
||||||
|
with open(RemoteConfiguration.WEB_CONFIG_CACHE, 'w') as f:
|
||||||
|
json.dump(config, f)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __load_cache(config):
|
||||||
|
"""
|
||||||
|
Load cache from file
|
||||||
|
"""
|
||||||
|
LOG.info("Using cached configuration if available")
|
||||||
|
ConfigurationLoader.load(config,
|
||||||
|
[RemoteConfiguration.WEB_CONFIG_CACHE],
|
||||||
|
False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __load_list(config, values):
|
def __load_list(config, values):
|
||||||
for v in values:
|
for v in values:
|
||||||
|
|
|
@ -187,8 +187,11 @@ class Lock(object): # python 3+ 'class Lock'
|
||||||
*args: Ignored. Required as this fuction is called as a signel
|
*args: Ignored. Required as this fuction is called as a signel
|
||||||
handler.
|
handler.
|
||||||
'''
|
'''
|
||||||
|
try:
|
||||||
with open(self.path, 'r') as L:
|
with open(self.path, 'r') as L:
|
||||||
if self.__pid == L.read():
|
pid = int(L.read())
|
||||||
|
if self.__pid == pid:
|
||||||
os.unlink(self.path)
|
os.unlink(self.path)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
# End class Lock
|
# End class Lock
|
||||||
|
|
|
@ -100,6 +100,17 @@ class WebsocketClient(object):
|
||||||
def remove(self, event_name, func):
|
def remove(self, event_name, func):
|
||||||
self.emitter.remove_listener(event_name, func)
|
self.emitter.remove_listener(event_name, func)
|
||||||
|
|
||||||
|
def remove_all_listeners(self, event_name):
|
||||||
|
'''
|
||||||
|
Remove all listeners connected to event_name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_name: event from which to remove listeners
|
||||||
|
'''
|
||||||
|
if event_name is None:
|
||||||
|
raise ValueError
|
||||||
|
self.emitter.remove_all_listeners(event_name)
|
||||||
|
|
||||||
def run_forever(self):
|
def run_forever(self):
|
||||||
self.client.run_forever()
|
self.client.run_forever()
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import tornado.ioloop as ioloop
|
import tornado.ioloop as ioloop
|
||||||
import tornado.web as web
|
import tornado.web as web
|
||||||
|
import tornado.autoreload as autoreload
|
||||||
|
|
||||||
from mycroft.configuration import ConfigurationManager
|
from mycroft.configuration import ConfigurationManager
|
||||||
from mycroft.messagebus.service.ws import WebsocketEventHandler
|
from mycroft.messagebus.service.ws import WebsocketEventHandler
|
||||||
|
@ -35,6 +36,13 @@ def main():
|
||||||
import tornado.options
|
import tornado.options
|
||||||
lock = Lock("service")
|
lock = Lock("service")
|
||||||
tornado.options.parse_command_line()
|
tornado.options.parse_command_line()
|
||||||
|
|
||||||
|
def reload_hook():
|
||||||
|
""" Hook to release lock when autoreload is triggered. """
|
||||||
|
lock.delete()
|
||||||
|
|
||||||
|
tornado.autoreload.add_reload_hook(reload_hook)
|
||||||
|
|
||||||
config = ConfigurationManager.get().get("websocket")
|
config = ConfigurationManager.get().get("websocket")
|
||||||
|
|
||||||
host = config.get("host")
|
host = config.get("host")
|
||||||
|
|
|
@ -44,7 +44,6 @@ class SkillSettings(dict):
|
||||||
def __init__(self, settings_file):
|
def __init__(self, settings_file):
|
||||||
super(SkillSettings, self).__init__()
|
super(SkillSettings, self).__init__()
|
||||||
self._path = settings_file
|
self._path = settings_file
|
||||||
|
|
||||||
# if file exist, open and read stored values into self
|
# if file exist, open and read stored values into self
|
||||||
if isfile(self._path):
|
if isfile(self._path):
|
||||||
with open(self._path) as f:
|
with open(self._path) as f:
|
||||||
|
@ -52,7 +51,11 @@ class SkillSettings(dict):
|
||||||
for key in json_data:
|
for key in json_data:
|
||||||
self.__setitem__(key, json_data[key])
|
self.__setitem__(key, json_data[key])
|
||||||
|
|
||||||
self._is_stored = True
|
self.loaded_hash = hash(str(self))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _is_stored(self):
|
||||||
|
return hash(str(self)) == self.loaded_hash
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return super(SkillSettings, self).__getitem__(key)
|
return super(SkillSettings, self).__getitem__(key)
|
||||||
|
@ -61,7 +64,6 @@ class SkillSettings(dict):
|
||||||
"""
|
"""
|
||||||
Add/Update key and note that the file needs saving.
|
Add/Update key and note that the file needs saving.
|
||||||
"""
|
"""
|
||||||
self._is_stored = False
|
|
||||||
return super(SkillSettings, self).__setitem__(key, value)
|
return super(SkillSettings, self).__setitem__(key, value)
|
||||||
|
|
||||||
def store(self):
|
def store(self):
|
||||||
|
@ -71,3 +73,4 @@ class SkillSettings(dict):
|
||||||
if not self._is_stored:
|
if not self._is_stored:
|
||||||
with open(self._path, 'w')as f:
|
with open(self._path, 'w')as f:
|
||||||
json.dump(self, f)
|
json.dump(self, f)
|
||||||
|
self.loaded_hash = hash(str(self))
|
||||||
|
|
|
@ -18,6 +18,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
|
from mycroft.util.json_helper import load_commented_json
|
||||||
|
|
||||||
SYSTEM_CONFIG = '/etc/mycroft/mycroft.conf'
|
SYSTEM_CONFIG = '/etc/mycroft/mycroft.conf'
|
||||||
|
|
||||||
|
@ -26,8 +27,7 @@ __author__ = 'seanfitz'
|
||||||
log_level = "DEBUG"
|
log_level = "DEBUG"
|
||||||
|
|
||||||
if isfile(SYSTEM_CONFIG):
|
if isfile(SYSTEM_CONFIG):
|
||||||
with open(SYSTEM_CONFIG) as f:
|
config = load_commented_json(SYSTEM_CONFIG)
|
||||||
config = json.load(f)
|
|
||||||
log_level = config.get("log_level", "DEBUG")
|
log_level = config.get("log_level", "DEBUG")
|
||||||
|
|
||||||
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
|
|
@ -26,7 +26,7 @@ __author__ = 'augustnmonteiro'
|
||||||
# START_VERSION_BLOCK
|
# START_VERSION_BLOCK
|
||||||
CORE_VERSION_MAJOR = 0
|
CORE_VERSION_MAJOR = 0
|
||||||
CORE_VERSION_MINOR = 8
|
CORE_VERSION_MINOR = 8
|
||||||
CORE_VERSION_BUILD = 16
|
CORE_VERSION_BUILD = 17
|
||||||
# END_VERSION_BLOCK
|
# END_VERSION_BLOCK
|
||||||
|
|
||||||
CORE_VERSION_STR = (str(CORE_VERSION_MAJOR) + "." +
|
CORE_VERSION_STR = (str(CORE_VERSION_MAJOR) + "." +
|
||||||
|
|
|
@ -43,3 +43,4 @@ inflection==0.3.1
|
||||||
uuid==1.30
|
uuid==1.30
|
||||||
pytz==2017.2
|
pytz==2017.2
|
||||||
pillow==4.1.1
|
pillow==4.1.1
|
||||||
|
mock
|
|
@ -0,0 +1,46 @@
|
||||||
|
from os.path import dirname, join, exists, isfile
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import unittest
|
||||||
|
import mock
|
||||||
|
from shutil import rmtree
|
||||||
|
from mycroft.lock import Lock
|
||||||
|
|
||||||
|
|
||||||
|
class TestLock(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
if exists('/tmp/mycroft'):
|
||||||
|
rmtree('/tmp/mycroft')
|
||||||
|
|
||||||
|
def test_create_lock(self):
|
||||||
|
l1 = Lock('test')
|
||||||
|
self.assertTrue(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
|
||||||
|
def test_delete_lock(self):
|
||||||
|
l1 = Lock('test')
|
||||||
|
self.assertTrue(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
l1.delete()
|
||||||
|
self.assertFalse(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
|
||||||
|
@mock.patch('os.kill')
|
||||||
|
def test_existing_lock(self, mock_kill):
|
||||||
|
""" Test that an existing lock will kill the old pid. """
|
||||||
|
l1 = Lock('test')
|
||||||
|
self.assertTrue(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
l2 = Lock('test2')
|
||||||
|
self.assertFalse(mock_kill.called)
|
||||||
|
l2 = Lock('test')
|
||||||
|
self.assertTrue(mock_kill.called)
|
||||||
|
|
||||||
|
def test_keyboard_interrupt(self):
|
||||||
|
l1 = Lock('test')
|
||||||
|
self.assertTrue(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
try:
|
||||||
|
os.kill(os.getpid(), signal.SIGINT)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
self.assertFalse(isfile('/tmp/mycroft/test.pid'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -36,3 +36,41 @@ class SkillSettingsTest(unittest.TestCase):
|
||||||
s2 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
s2 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
for key in s:
|
for key in s:
|
||||||
self.assertEqual(s[key], s2[key])
|
self.assertEqual(s[key], s2[key])
|
||||||
|
|
||||||
|
def test_update_list(self):
|
||||||
|
s = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
s['l'] = ['a', 'b', 'c']
|
||||||
|
s.store()
|
||||||
|
s2 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
self.assertEqual(s['l'], s2['l'])
|
||||||
|
|
||||||
|
# Update list
|
||||||
|
s2['l'].append('d')
|
||||||
|
s2.store()
|
||||||
|
s3 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
self.assertEqual(s2['l'], s3['l'])
|
||||||
|
|
||||||
|
def test_update_dict(self):
|
||||||
|
s = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
s['d'] = {'a': 1, 'b': 2}
|
||||||
|
s.store()
|
||||||
|
s2 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
self.assertEqual(s['d'], s2['d'])
|
||||||
|
|
||||||
|
# Update dict
|
||||||
|
s2['d']['c'] = 3
|
||||||
|
s2.store()
|
||||||
|
s3 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
self.assertEqual(s2['d'], s3['d'])
|
||||||
|
|
||||||
|
def test_no_change(self):
|
||||||
|
s = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
s['d'] = {'a': 1, 'b': 2}
|
||||||
|
s.store()
|
||||||
|
|
||||||
|
s = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
|
||||||
|
self.assertTrue(s._is_stored)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue