Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deprecate!:backend client #604

Merged
merged 11 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 194 additions & 4 deletions mycroft/skills/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,207 @@
"""
import json
from os.path import isfile
import yaml

from threading import Timer
from typing import Optional

import yaml
from ovos_backend_client.api import DeviceApi
from ovos_backend_client.pairing import is_paired, requires_backend
from ovos_utils.log import LOG
from ovos_backend_client.settings import RemoteSkillSettings, get_display_name

from ovos_bus_client.message import Message, dig_for_message
from ovos_utils.log import LOG

# backwards compat imports
from mycroft.deprecated.skills.settings import SkillSettingsDownloader, SettingsMetaUploader, load_remote_settings_cache, \
save_remote_settings_cache, REMOTE_CACHE
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved

from ovos_workshop.settings import SkillSettingsManager

class SkillSettingsManager:
def __init__(self, skill):
self.download_timer: Optional[Timer] = None
self.skill = skill
self.api = DeviceApi()
self.remote_settings = \
RemoteSkillSettings(self.skill_id,
settings=dict(self.skill.settings),
meta=self.load_meta(), remote_id=self.skill_gid)
self.register_bus_handlers()

def start(self):
self._download()

def _download(self):
# If this method is called outside of the timer loop, ensure the
# existing timer is canceled before starting a new one.
if self.download_timer:
self.download_timer.cancel()

self.download()

# prepare to download again in 60 seconds
self.download_timer = Timer(60, self._download)
self.download_timer.daemon = True
self.download_timer.start()

def stop(self):
# If this method is called outside of the timer loop, ensure the
# existing timer is canceled
if self.download_timer:
self.download_timer.cancel()

@property
def bus(self):
return self.skill.bus

@property
def skill_id(self) -> str:
return self.skill.skill_id

@property
def display_name(self) -> str:
return get_display_name(self.skill_id)

@property
def skill_gid(self) -> str:
return f"@{self.api.uuid}|{self.skill_id}"

@property
def skill_meta(self) -> dict:
return self.remote_settings.meta

def register_bus_handlers(self):
self.skill.add_event('mycroft.skills.settings.update',
self.handle_download_remote) # backwards compat
self.skill.add_event('mycroft.skills.settings.download',
self.handle_download_remote)
self.skill.add_event('mycroft.skills.settings.upload',
self.handle_upload_local)
self.skill.add_event('mycroft.skills.settings.upload.meta',
self.handle_upload_meta)
self.skill.add_event('mycroft.paired',
self.handle_upload_local)

def load_meta(self) -> dict:
json_path = f"{self.skill.root_dir}/settingsmeta.json"
yaml_path = f"{self.skill.root_dir}/settingsmeta.yaml"
if isfile(yaml_path):
with open(yaml_path) as meta_file:
return yaml.safe_load(meta_file)
elif isfile(json_path):
with open(json_path) as meta_file:
return json.load(meta_file)
return {}

def save_meta(self, generate: bool = False):
# unset reload flag to avoid a reload on settingmeta change
# TODO - support for settingsmeta XDG paths
reload = self.skill.reload_skill
self.skill.reload_skill = False

# generate meta for missing fields
if generate:
self.remote_settings.generate_meta()

# write to disk
json_path = f"{self.skill.root_dir}/settingsmeta.json"
yaml_path = f"{self.skill.root_dir}/settingsmeta.yaml"
if isfile(yaml_path):
with open(yaml_path) as meta_file:
yaml.dump(self.remote_settings.meta, meta_file)
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved
else:
with open(json_path, "w") as meta_file:
json.dump(self.remote_settings.meta, meta_file)

# reset reloading flag
self.skill.reload_skill = reload

@requires_backend
def upload(self, generate: bool = False):
if not is_paired():
LOG.debug("Device needs to be paired to upload settings")
return
self.remote_settings.settings = dict(self.skill.settings)
if generate:
self.remote_settings.generate_meta()
self.remote_settings.upload()

@requires_backend
def upload_meta(self, generate: bool = False):
if not is_paired():
LOG.debug("Device needs to be paired to upload settingsmeta")
return
if generate:
self.remote_settings.settings = dict(self.skill.settings)
self.remote_settings.generate_meta()
self.remote_settings.upload_meta()

@requires_backend
def download(self):
if not is_paired():
LOG.debug("Device needs to be paired to download remote settings")
return
self.remote_settings.download()
# we do not update skill object settings directly
# skill will handle the event and trigger a callback
if self.skill.settings != self.remote_settings.settings:
# dig old message to keep context
msg = dig_for_message() or Message("")
msg = msg.forward('mycroft.skills.settings.changed')

msg.data[self.skill_id] = self.remote_settings.settings
self.bus.emit(msg)

def handle_upload_meta(self, message: Message):
skill_id = message.data.get("skill_id")
if skill_id == self.skill_id:
self.upload_meta()

def handle_upload_local(self, message: Message):
skill_id = message.data.get("skill_id")
if skill_id == self.skill_id:
self.upload()

def handle_download_remote(self, message: Message):
self.download()


def settings2meta(settings, section_name="Skill Settings"):
""" generates basic settingsmeta """
fields = []

for k, v in settings.items():
if k.startswith("_"):
continue
label = k.replace("-", " ").replace("_", " ").title()
if isinstance(v, bool):
fields.append({
"name": k,
"type": "checkbox",
"label": label,
"value": str(v).lower()
})
if isinstance(v, str):
fields.append({
"name": k,
"type": "text",
"label": label,
"value": v
})
if isinstance(v, int):
fields.append({
"name": k,
"type": "number",
"label": label,
"value": str(v)
})
return {
"skillMetadata": {
"sections": [
{
"name": section_name,
"fields": fields
}
]
}
}
1 change: 1 addition & 0 deletions requirements/extra-deprecated.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pillow>=8.3
pyaudio

# mycroft-core imports / default plugins
ovos-backend-client>=0.1.0,<2.0.0
# for compat with mycroft namespace
ovos-lingua-franca>=0.4.7,<1.0.0
# NOTE: ovos-listener is causing conflicts in dependency resolution, blocks ovos-bus-client 1.0.0
Expand Down
1 change: 0 additions & 1 deletion requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ ovos-utils>=0.3.5,<1.0.0
ovos_bus_client>=0.1.4,<2.0.0
ovos-plugin-manager>=0.5.6,<1.0.0
ovos-config>=0.0.13,<1.0.0
ovos-backend-client>=0.1.0,<2.0.0
ovos-workshop>=2.2.2,<3.0.0
Loading