Repository: MycroftAI/mycroft-core

Issue:
Add local settings/storage for skills
A mechanism for skills to store settings and data during and between runs. The data should be stored in the skill directory.


Discussion:

Resolving patch:
diff --git a/mycroft/skills/core.py b/mycroft/skills/core.py
--- a/mycroft/skills/core.py
+++ b/mycroft/skills/core.py
@@ -34,7 +34,7 @@
 from mycroft.filesystem import FileSystemAccess
 from mycroft.messagebus.message import Message
 from mycroft.util.log import getLogger
-
+from mycroft.skills.settings import SkillSettings
 __author__ = 'seanfitz'
 
 BLACKLISTED_SKILLS = ["send_sms", "media"]
@@ -105,6 +105,7 @@ def load_skill(skill_descriptor, emitter):
             # v2 skills framework
             skill = skill_module.create_skill()
             skill.bind(emitter)
+            skill._dir = dirname(skill_descriptor['info'][1])
             skill.load_data_files(dirname(skill_descriptor['info'][1]))
             skill.initialize()
             logger.info("Loaded " + skill_descriptor["name"])
@@ -206,6 +207,15 @@ def location_timezone(self):
     def lang(self):
         return self.config_core.get('lang')
 
+    @property
+    def settings(self):
+        """ Load settings if not already loaded. """
+        try:
+            return self._settings
+        except:
+            self._settings = SkillSettings(join(self._dir, 'settings.json'))
+            return self._settings
+
     def bind(self, emitter):
         if emitter:
             self.emitter = emitter
@@ -331,6 +341,8 @@ def shutdown(self):
         process termination. The skill implementation must
         shutdown all processes and operations in execution.
         """
+        # Store settings
+        self.settings.store()
 
         # removing events
         for e, f in self.events:
diff --git a/mycroft/skills/settings.py b/mycroft/skills/settings.py
new file mode 100644
--- /dev/null
+++ b/mycroft/skills/settings.py
@@ -0,0 +1,73 @@
+# Copyright 2017 Mycroft AI, Inc.
+#
+# This file is part of Mycroft Core.
+#
+# Mycroft Core is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Mycroft Core is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mycroft Core.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+    This module provides the SkillSettings dictionary, which is a simple
+    extension of the python dict to enable storing.
+
+    Example:
+        from mycroft.skill.settings import SkillSettings
+
+        s = SkillSettings('./settings.json')
+        s['meaning of life'] = 42
+        s['flower pot sayings'] = 'Not again...'
+        s.store()
+"""
+
+import json
+import sys
+from os.path import isfile
+
+
+class SkillSettings(dict):
+    """
+        SkillSettings creates a dictionary that can easily be stored
+        to file, serialized as json.
+
+        Args:
+            settings_file (str): Path to storage file
+    """
+    def __init__(self, settings_file):
+        super(SkillSettings, self).__init__()
+        self._path = settings_file
+
+        # if file exist, open and read stored values into self
+        if isfile(self._path):
+            with open(self._path) as f:
+                json_data = json.load(f)
+                for key in json_data:
+                    self.__setitem__(key, json_data[key])
+
+        self._is_stored = True
+
+    def __getitem__(self, key):
+        return super(SkillSettings, self).__getitem__(key)
+
+    def __setitem__(self, key, value):
+        """
+            Add/Update key and note that the file needs saving.
+        """
+        self._is_stored = False
+        return super(SkillSettings, self).__setitem__(key, value)
+
+    def store(self):
+        """
+            Store dictionary to file if it has changed
+        """
+        if not self._is_stored:
+            with open(self._path, 'w')as f:
+                json.dump(self, f)


Test patch:
diff --git a/test/skills/settings.py b/test/skills/settings.py
new file mode 100644
--- /dev/null
+++ b/test/skills/settings.py
@@ -0,0 +1,38 @@
+from mycroft.skills.settings import SkillSettings
+
+from os.path import join, dirname, abspath
+from os import remove
+import unittest
+
+
+class SkillSettingsTest(unittest.TestCase):
+    def setUp(self):
+        try:
+            remove(join(dirname(__file__), 'settings', 'store.json'))
+        except OSError:
+            pass
+
+    def test_new(self):
+        s = SkillSettings(join(dirname(__file__), 'settings.json'))
+        self.assertEqual(len(s), 0)
+
+    def test_add_value(self):
+        s = SkillSettings(join(dirname(__file__), 'settings.json'))
+        s['test_val'] = 1
+
+    def test_load_existing(self):
+        s = SkillSettings(join(dirname(__file__), 'settings', 'existing.json'))
+        self.assertEqual(len(s), 4)
+
+    def test_store(self):
+        s = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
+        s['bool'] = True
+        s['int'] = 42
+        s['float'] = 4.2
+        s['string'] = 'Always carry a towel'
+        s['list'] = ['batman', 2, True, 'superman']
+        s.store()
+
+        s2 = SkillSettings(join(dirname(__file__), 'settings', 'store.json'))
+        for key in s:
+            self.assertEqual(s[key], s2[key])
diff --git a/test/skills/settings/existing.json b/test/skills/settings/existing.json
new file mode 100644
--- /dev/null
+++ b/test/skills/settings/existing.json
@@ -0,0 +1,7 @@
+{
+    "a": 1,
+    "b": 2,
+    "c": 3,
+    "d": 4
+}
+