aboutsummaryrefslogtreecommitdiffstats
path: root/testing/mycroft-core/0002-follow-xdg-for-skill-settings.patch
blob: 6caf8370699d4e39d7b058921b5d6ba98fd700af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
Upstream pull-request: https://github.com/MycroftAI/mycroft-core/pull/2559

diff --git a/mycroft/skills/mycroft_skill/mycroft_skill.py b/mycroft/skills/mycroft_skill/mycroft_skill.py
index 4f3b6d9da6..d60a3279d0 100644
--- a/mycroft/skills/mycroft_skill/mycroft_skill.py
+++ b/mycroft/skills/mycroft_skill/mycroft_skill.py
@@ -21,8 +21,11 @@ import traceback
 from itertools import chain
 from os import walk
 from os.path import join, abspath, dirname, basename, exists
+from pathlib import Path
 from threading import Event, Timer
 
+from xdg import BaseDirectory
+
 from adapt.intent import Intent, IntentBuilder
 
 from mycroft import dialog
@@ -113,6 +116,7 @@ class MycroftSkill:
         bus (MycroftWebsocketClient): Optional bus connection
         use_settings (bool): Set to false to not use skill settings at all
     """
+
     def __init__(self, name=None, bus=None, use_settings=True):
         self.name = name or self.__class__.__name__
         self.resting_name = None
@@ -123,16 +127,6 @@ class MycroftSkill:
         #: Member variable containing the absolute path of the skill's root
         #: directory. E.g. /opt/mycroft/skills/my-skill.me/
         self.root_dir = dirname(abspath(sys.modules[self.__module__].__file__))
-        if use_settings:
-            self.settings = get_local_settings(self.root_dir, self.name)
-            self._initial_settings = deepcopy(self.settings)
-        else:
-            self.settings = None
-
-        #: Set to register a callback method that will be called every time
-        #: the skills settings are updated. The referenced method should
-        #: include any logic needed to handle the updated settings.
-        self.settings_change_callback = None
 
         self.gui = SkillGUI(self)
 
@@ -141,6 +135,19 @@ class MycroftSkill:
         self.bind(bus)
         #: Mycroft global configuration. (dict)
         self.config_core = Configuration.get()
+
+        self.settings = None
+        self.settings_write_path = None
+        self.settings_read_path = None
+
+        if use_settings:
+            self._init_settings()
+
+        #: Set to register a callback method that will be called every time
+        #: the skills settings are updated. The referenced method should
+        #: include any logic needed to handle the updated settings.
+        self.settings_change_callback = None
+
         self.dialog_renderer = None
 
         #: Filesystem access to skill specific folder.
@@ -157,6 +164,42 @@ class MycroftSkill:
         self.event_scheduler = EventSchedulerInterface(self.name)
         self.intent_service = IntentServiceInterface()
 
+    def _init_settings(self):
+        """Setup skill settings."""
+
+        # To not break existing setups,
+        # save to skill directory if the file exists already
+        self.settings_write_path = Path(self.root_dir)
+
+        # Otherwise save to XDG_CONFIG_DIR
+        if not self.settings_write_path.joinpath('settings.json').exists():
+            self.settings_write_path = Path(BaseDirectory.save_config_path(
+                'mycroft', 'skills', basename(self.root_dir)))
+
+        # To not break existing setups,
+        # read from skill directory if the settings file exists there
+        self.settings_read_path = Path(self.root_dir)
+
+        # Then, check XDG_CONFIG_DIR
+        if not self.settings_read_path.joinpath('settings.json').exists():
+            for dir in BaseDirectory.load_config_paths('mycroft',
+                                                       'skills',
+                                                       basename(
+                                                           self.root_dir)):
+                path = Path(dir)
+                #: If there is a settings file here, use it
+                if path.joinpath('settings.json').exists():
+                    self.settings_read_path = path
+                    break
+
+        # Lastly, check /etc/mycroft
+        if not self.settings_read_path.joinpath('settings.json').exists():
+            self.settings_read_path = Path(
+                '/etc/mycroft/skills/').joinpath(basename(self.root_dir))
+
+        self.settings = get_local_settings(self.settings_read_path, self.name)
+        self._initial_settings = deepcopy(self.settings)
+
     @property
     def enclosure(self):
         if self._enclosure:
@@ -271,7 +314,7 @@ class MycroftSkill:
             if remote_settings is not None:
                 LOG.info('Updating settings for skill ' + self.name)
                 self.settings.update(**remote_settings)
-                save_settings(self.root_dir, self.settings)
+                save_settings(self.settings_write_path, self.settings)
                 if self.settings_change_callback is not None:
                     self.settings_change_callback()
 
@@ -544,7 +587,7 @@ class MycroftSkill:
 
             if not voc or not exists(voc):
                 raise FileNotFoundError(
-                        'Could not find {}.voc file'.format(voc_filename))
+                    'Could not find {}.voc file'.format(voc_filename))
             # load vocab and flatten into a simple list
             vocab = read_vocab_file(voc)
             self.voc_match_cache[cache_key] = list(chain(*vocab))
@@ -811,7 +854,7 @@ class MycroftSkill:
             """Store settings and indicate that the skill handler has completed
             """
             if self.settings != self._initial_settings:
-                save_settings(self.root_dir, self.settings)
+                save_settings(self.settings_write_path, self.settings)
                 self._initial_settings = self.settings
             if handler_info:
                 msg_type = handler_info + '.complete'
@@ -1233,7 +1276,7 @@ class MycroftSkill:
 
         # Store settings
         if self.settings != self._initial_settings:
-            save_settings(self.root_dir, self.settings)
+            save_settings(self.settings_write_path, self.settings)
 
         if self.settings_meta:
             self.settings_meta.stop()
diff --git a/mycroft/skills/settings.py b/mycroft/skills/settings.py
index c210625f5b..f576b34c9e 100644
--- a/mycroft/skills/settings.py
+++ b/mycroft/skills/settings.py
@@ -93,18 +93,22 @@ def get_local_settings(skill_dir, skill_name) -> dict:
 def save_settings(skill_dir, skill_settings):
     """Save skill settings to file."""
     settings_path = Path(skill_dir).joinpath('settings.json')
-    if Path(skill_dir).exists():
-        with open(str(settings_path), 'w') as settings_file:
-            try:
-                json.dump(skill_settings, settings_file)
-            except Exception:
-                LOG.exception('error saving skill settings to '
-                              '{}'.format(settings_path))
-            else:
-                LOG.info('Skill settings successfully saved to '
-                         '{}' .format(settings_path))
-    else:
-        LOG.info('Skill folder no longer exists, can\'t save settings.')
+
+    # Either the file already exists in /opt, or we are writing
+    # to XDG_CONFIG_DIR and always have the permission to make
+    # sure the file always exists
+    if not Path(settings_path).exists():
+        settings_path.touch(mode=0o644)
+
+    with open(str(settings_path), 'w') as settings_file:
+        try:
+            json.dump(skill_settings, settings_file)
+        except Exception:
+            LOG.exception('error saving skill settings to '
+                          '{}'.format(settings_path))
+        else:
+            LOG.info('Skill settings successfully saved to '
+                     '{}' .format(settings_path))
 
 
 def get_display_name(skill_name: str):
diff --git a/mycroft/skills/skill_loader.py b/mycroft/skills/skill_loader.py
index 2402740188..b7e8186967 100644
--- a/mycroft/skills/skill_loader.py
+++ b/mycroft/skills/skill_loader.py
@@ -259,7 +259,8 @@ class SkillLoader:
         if first_run:
             LOG.info("First run of " + self.skill_id)
             self.instance.settings["__mycroft_skill_firstrun"] = False
-            save_settings(self.skill_directory, self.instance.settings)
+            save_settings(self.instance.settings_write_path,
+                          self.instance.settings)
             intro = self.instance.get_intro_message()
             if intro:
                 self.instance.speak(intro)
diff --git a/requirements.txt b/requirements.txt
index 509436fb89..c9358db687 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -30,3 +30,4 @@ fann2==1.0.7
 padaos==0.1.9
 precise-runner==0.2.1
 petact==0.1.2
+pyxdg==0.26