We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Ubuntu 22.10's upgrade to 1.1.5 broke stuff. ("'NoneType' object is not iterable") w/fix
Hey fwiw in Ubuntu 22.10 (beta), I just got an upgrade of solaar from 1.1.1 to 1.1.5 ([solaar_1.1.5+dfsg-1_all.deb](https://packages.ubuntu.com/kinetic/solaar)) and noticed that solaar was now broken.  Running it manually resulted in an error that ended like this:

```
....
  File "/usr/share/solaar/lib/logitech_receiver/device.py", line 352, in persister
    self._persister = _configuration.persister(self)
  File "/usr/share/solaar/lib/solaar/configuration.py", line 214, in persister
    _load()
  File "/usr/share/solaar/lib/solaar/configuration.py", line 71, in _load
    _config = _cleanup_load(loaded_config)
  File "/usr/share/solaar/lib/solaar/configuration.py", line 137, in _cleanup_load
    for element in c:
TypeError: 'NoneType' object is not iterable
```

This was running as the user (not rute) and seemed to be an issue parsing the `~/.config/solaar/config.yaml` file.  When I looked at that file, it was completely blank, though there was a file there `config.json` modified five months back that looked like this:

```
{
  "_version": "1.1.1"
}
```

On a hunch, I set the blank `config.yaml` to instead look like:

```
_version:1.1.5
```

and started solar and it came back!  It repopulated the config.yaml, so I'm guessing it just wanted any values in there so it wouldn't error out.

While this is probably a bug w/ubuntu's packaging and may even be due to me running as a normal user, the `configuration.yaml` file's `_cleanup_load(c)` function should probably gracefully behave if there are no yaml entries in there rather than silently crashing.

That is all!  This is probably going to have to also be addressed in the Ubuntu release, but I figured this was upstream so maybe it should be here as well.  Thanks!


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/solaar/configuration.py`
Content:
```
1 # -*- python-mode -*-
2 
3 ## Copyright (C) 2012-2013  Daniel Pavel
4 ##
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published by
7 ## the Free Software Foundation; either version 2 of the License, or
8 ## (at your option) any later version.
9 ##
10 ## This program is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ## GNU General Public License for more details.
14 ##
15 ## You should have received a copy of the GNU General Public License along
16 ## with this program; if not, write to the Free Software Foundation, Inc.,
17 ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 
19 import json as _json
20 import os as _os
21 import os.path as _path
22 
23 from logging import DEBUG as _DEBUG
24 from logging import INFO as _INFO
25 from logging import getLogger
26 from threading import Lock as _Lock
27 from threading import Timer as _Timer
28 
29 import yaml as _yaml
30 
31 from gi.repository import GLib
32 from logitech_receiver.common import NamedInt as _NamedInt
33 from solaar import __version__
34 
35 _log = getLogger(__name__)
36 del getLogger
37 
38 _XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))
39 _file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')
40 _yaml_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.yaml')
41 
42 _KEY_VERSION = '_version'
43 _KEY_NAME = '_NAME'
44 _KEY_WPID = '_wpid'
45 _KEY_SERIAL = '_serial'
46 _KEY_MODEL_ID = '_modelId'
47 _KEY_UNIT_ID = '_unitId'
48 _KEY_ABSENT = '_absent'
49 _KEY_SENSITIVE = '_sensitive'
50 _config = []
51 
52 
53 def _load():
54     global _config
55     loaded_config = []
56     if _path.isfile(_yaml_file_path):
57         try:
58             with open(_yaml_file_path) as config_file:
59                 loaded_config = _yaml.safe_load(config_file)
60         except Exception as e:
61             _log.error('failed to load from %s: %s', _yaml_file_path, e)
62     elif _path.isfile(_file_path):
63         try:
64             with open(_file_path) as config_file:
65                 loaded_config = _json.load(config_file)
66         except Exception as e:
67             _log.error('failed to load from %s: %s', _file_path, e)
68         loaded_config = _convert_json(loaded_config)
69     if _log.isEnabledFor(_DEBUG):
70         _log.debug('load => %s', loaded_config)
71     _config = _cleanup_load(loaded_config)
72 
73 
74 save_timer = None
75 save_lock = _Lock()
76 
77 
78 def save(defer=False):
79     global save_timer
80     if not _config:
81         return
82     dirname = _os.path.dirname(_yaml_file_path)
83     if not _path.isdir(dirname):
84         try:
85             _os.makedirs(dirname)
86         except Exception:
87             _log.error('failed to create %s', dirname)
88             return
89     if not defer:
90         do_save()
91     else:
92         with save_lock:
93             if not save_timer:
94                 save_timer = _Timer(5.0, lambda: GLib.idle_add(do_save))
95                 save_timer.start()
96 
97 
98 def do_save():
99     global save_timer
100     with save_lock:
101         if save_timer:
102             save_timer.cancel()
103             save_timer = None
104         try:
105             with open(_yaml_file_path, 'w') as config_file:
106                 _yaml.dump(_config, config_file, default_flow_style=None, width=150)
107             if _log.isEnabledFor(_INFO):
108                 _log.info('saved %s to %s', _config, _yaml_file_path)
109         except Exception as e:
110             _log.error('failed to save to %s: %s', _yaml_file_path, e)
111 
112 
113 def _convert_json(json_dict):
114     config = [json_dict.get(_KEY_VERSION)]
115     for key, dev in json_dict.items():
116         key = key.split(':')
117         if len(key) == 2:
118             dev[_KEY_WPID] = dev.get(_KEY_WPID) if dev.get(_KEY_WPID) else key[0]
119             dev[_KEY_SERIAL] = dev.get(_KEY_SERIAL) if dev.get(_KEY_SERIAL) else key[1]
120             for k, v in dev.items():
121                 if type(k) == str and not k.startswith('_') and type(v) == dict:  # convert string keys to ints
122                     v = {int(dk) if type(dk) == str else dk: dv for dk, dv in v.items()}
123                 dev[k] = v
124             for k in ['mouse-gestures', 'dpi-sliding']:
125                 v = dev.get(k, None)
126                 if v is True or v is False:
127                     dev.pop(k)
128             if '_name' in dev:
129                 dev[_KEY_NAME] = dev['_name']
130                 dev.pop('_name')
131             config.append(dev)
132     return config
133 
134 
135 def _cleanup_load(c):
136     _config = [__version__]
137     for element in c:
138         if isinstance(element, dict):
139             divert = element.get('divert-keys')
140             if divert:
141                 sliding = element.get('dpi-sliding')
142                 if sliding:  # convert old-style dpi-sliding setting to divert-keys entry
143                     divert[int(sliding)] = 3
144                 element.pop('dpi-sliding', None)
145                 gestures = element.get('mouse-gestures')
146                 if gestures:  # convert old-style mouse-gestures setting to divert-keys entry
147                     divert[int(gestures)] = 2
148                 element.pop('mouse-gestures', None)
149                 # remove any string entries (from bad conversions)
150                 element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}
151             # convert to device entries
152             element = _DeviceEntry(**element)
153             _config.append(element)
154     return _config
155 
156 
157 class _DeviceEntry(dict):
158     def __init__(self, **kwargs):
159         super().__init__(**kwargs)
160 
161     def __setitem__(self, key, value):
162         super().__setitem__(key, value)
163         save(defer=True)
164 
165     def update(self, device, modelId):
166         if device.name and device.name != self.get(_KEY_NAME):
167             super().__setitem__(_KEY_NAME, device.name)
168         if device.wpid and device.wpid != self.get(_KEY_WPID):
169             super().__setitem__(_KEY_WPID, device.wpid)
170         if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL):
171             super().__setitem__(_KEY_SERIAL, device.serial)
172         if modelId and modelId != self.get(_KEY_MODEL_ID):
173             super().__setitem__(_KEY_MODEL_ID, modelId)
174         if device.unitId and device.unitId != self.get(_KEY_UNIT_ID):
175             super().__setitem__(_KEY_UNIT_ID, device.unitId)
176 
177     def get_sensitivity(self, name):
178         return self.get(_KEY_SENSITIVE, {}).get(name, False)
179 
180     def set_sensitivity(self, name, value):
181         sensitives = self.get(_KEY_SENSITIVE, {})
182         if sensitives.get(name) != value:
183             sensitives[name] = value
184             self.__setitem__(_KEY_SENSITIVE, sensitives)
185 
186 
187 def device_representer(dumper, data):
188     return dumper.represent_mapping('tag:yaml.org,2002:map', data)
189 
190 
191 _yaml.add_representer(_DeviceEntry, device_representer)
192 
193 
194 def named_int_representer(dumper, data):
195     return dumper.represent_scalar('tag:yaml.org,2002:int', str(int(data)))
196 
197 
198 _yaml.add_representer(_NamedInt, named_int_representer)
199 
200 
201 # A device can be identified by a combination of WPID and serial number (for receiver-connected devices)
202 # or a combination of modelId and unitId (for direct-connected devices).
203 # But some devices have empty (all zero) modelIds and unitIds.  Use the device name as a backup for the modelId.
204 # The worst situation is a receiver-connected device that Solaar has never seen on-line
205 # that is directly connected.  Here there is no way to realize that the two devices are the same.
206 # So new entries are not created for unseen off-line receiver-connected devices except for those with protocol 1.0
207 def persister(device):
208     def match(wpid, serial, modelId, unitId, c):
209         return ((wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or (
210             modelId and modelId != '000000000000' and modelId == c.get(_KEY_MODEL_ID) and unitId
211             and unitId == c.get(_KEY_UNIT_ID)
212         ))
213 
214     if not _config:
215         _load()
216     entry = None
217     modelId = device.modelId if device.modelId != '000000000000' else device.name if device.modelId else None
218     for c in _config:
219         if isinstance(c, _DeviceEntry) and match(device.wpid, device.serial, modelId, device.unitId, c):
220             entry = c
221             break
222     if not entry:
223         if not device.online and not device.serial:  # don't create entry for offline devices without serial number
224             if _log.isEnabledFor(_INFO):
225                 _log.info('not setting up persister for offline device %s with missing serial number', device.name)
226             return
227         if _log.isEnabledFor(_INFO):
228             _log.info('setting up persister for device %s', device.name)
229         entry = _DeviceEntry()
230         _config.append(entry)
231     entry.update(device, modelId)
232     return entry
233 
234 
235 def attach_to(device):
236     pass
237
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py
--- a/lib/solaar/configuration.py
+++ b/lib/solaar/configuration.py
@@ -134,23 +134,26 @@
 
 def _cleanup_load(c):
     _config = [__version__]
-    for element in c:
-        if isinstance(element, dict):
-            divert = element.get('divert-keys')
-            if divert:
-                sliding = element.get('dpi-sliding')
-                if sliding:  # convert old-style dpi-sliding setting to divert-keys entry
-                    divert[int(sliding)] = 3
-                element.pop('dpi-sliding', None)
-                gestures = element.get('mouse-gestures')
-                if gestures:  # convert old-style mouse-gestures setting to divert-keys entry
-                    divert[int(gestures)] = 2
-                element.pop('mouse-gestures', None)
-                # remove any string entries (from bad conversions)
-                element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}
-            # convert to device entries
-            element = _DeviceEntry(**element)
-            _config.append(element)
+    try:
+        for element in c:
+            if isinstance(element, dict):
+                divert = element.get('divert-keys')
+                if divert:
+                    sliding = element.get('dpi-sliding')
+                    if sliding:  # convert old-style dpi-sliding setting to divert-keys entry
+                        divert[int(sliding)] = 3
+                    element.pop('dpi-sliding', None)
+                    gestures = element.get('mouse-gestures')
+                    if gestures:  # convert old-style mouse-gestures setting to divert-keys entry
+                        divert[int(gestures)] = 2
+                    element.pop('mouse-gestures', None)
+                    # remove any string entries (from bad conversions)
+                    element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}
+                # convert to device entries
+                element = _DeviceEntry(**element)
+                _config.append(element)
+    except Exception as e:
+        _log.warn('Exception processing config.yaml file, ignoring contents: %s', e)
     return _config

{"golden_diff": "diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py\n--- a/lib/solaar/configuration.py\n+++ b/lib/solaar/configuration.py\n@@ -134,23 +134,26 @@\n \n def _cleanup_load(c):\n     _config = [__version__]\n-    for element in c:\n-        if isinstance(element, dict):\n-            divert = element.get('divert-keys')\n-            if divert:\n-                sliding = element.get('dpi-sliding')\n-                if sliding:  # convert old-style dpi-sliding setting to divert-keys entry\n-                    divert[int(sliding)] = 3\n-                element.pop('dpi-sliding', None)\n-                gestures = element.get('mouse-gestures')\n-                if gestures:  # convert old-style mouse-gestures setting to divert-keys entry\n-                    divert[int(gestures)] = 2\n-                element.pop('mouse-gestures', None)\n-                # remove any string entries (from bad conversions)\n-                element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}\n-            # convert to device entries\n-            element = _DeviceEntry(**element)\n-            _config.append(element)\n+    try:\n+        for element in c:\n+            if isinstance(element, dict):\n+                divert = element.get('divert-keys')\n+                if divert:\n+                    sliding = element.get('dpi-sliding')\n+                    if sliding:  # convert old-style dpi-sliding setting to divert-keys entry\n+                        divert[int(sliding)] = 3\n+                    element.pop('dpi-sliding', None)\n+                    gestures = element.get('mouse-gestures')\n+                    if gestures:  # convert old-style mouse-gestures setting to divert-keys entry\n+                        divert[int(gestures)] = 2\n+                    element.pop('mouse-gestures', None)\n+                    # remove any string entries (from bad conversions)\n+                    element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}\n+                # convert to device entries\n+                element = _DeviceEntry(**element)\n+                _config.append(element)\n+    except Exception as e:\n+        _log.warn('Exception processing config.yaml file, ignoring contents: %s', e)\n     return _config\n", "issue": "Ubuntu 22.10's upgrade to 1.1.5 broke stuff. (\"'NoneType' object is not iterable\") w/fix\nHey fwiw in Ubuntu 22.10 (beta), I just got an upgrade of solaar from 1.1.1 to 1.1.5 ([solaar_1.1.5+dfsg-1_all.deb](https://packages.ubuntu.com/kinetic/solaar)) and noticed that solaar was now broken.  Running it manually resulted in an error that ended like this:\r\n\r\n```\r\n....\r\n  File \"/usr/share/solaar/lib/logitech_receiver/device.py\", line 352, in persister\r\n    self._persister = _configuration.persister(self)\r\n  File \"/usr/share/solaar/lib/solaar/configuration.py\", line 214, in persister\r\n    _load()\r\n  File \"/usr/share/solaar/lib/solaar/configuration.py\", line 71, in _load\r\n    _config = _cleanup_load(loaded_config)\r\n  File \"/usr/share/solaar/lib/solaar/configuration.py\", line 137, in _cleanup_load\r\n    for element in c:\r\nTypeError: 'NoneType' object is not iterable\r\n```\r\n\r\nThis was running as the user (not rute) and seemed to be an issue parsing the `~/.config/solaar/config.yaml` file.  When I looked at that file, it was completely blank, though there was a file there `config.json` modified five months back that looked like this:\r\n\r\n```\r\n{\r\n  \"_version\": \"1.1.1\"\r\n}\r\n```\r\n\r\nOn a hunch, I set the blank `config.yaml` to instead look like:\r\n\r\n```\r\n_version:1.1.5\r\n```\r\n\r\nand started solar and it came back!  It repopulated the config.yaml, so I'm guessing it just wanted any values in there so it wouldn't error out.\r\n\r\nWhile this is probably a bug w/ubuntu's packaging and may even be due to me running as a normal user, the `configuration.yaml` file's `_cleanup_load(c)` function should probably gracefully behave if there are no yaml entries in there rather than silently crashing.\r\n\r\nThat is all!  This is probably going to have to also be addressed in the Ubuntu release, but I figured this was upstream so maybe it should be here as well.  Thanks!\r\n\n", "before_files": [{"content": "# -*- python-mode -*-\n\n## Copyright (C) 2012-2013  Daniel Pavel\n##\n## This program is free software; you can redistribute it and/or modify\n## it under the terms of the GNU General Public License as published by\n## the Free Software Foundation; either version 2 of the License, or\n## (at your option) any later version.\n##\n## This program is distributed in the hope that it will be useful,\n## but WITHOUT ANY WARRANTY; without even the implied warranty of\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n## GNU General Public License for more details.\n##\n## You should have received a copy of the GNU General Public License along\n## with this program; if not, write to the Free Software Foundation, Inc.,\n## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nimport json as _json\nimport os as _os\nimport os.path as _path\n\nfrom logging import DEBUG as _DEBUG\nfrom logging import INFO as _INFO\nfrom logging import getLogger\nfrom threading import Lock as _Lock\nfrom threading import Timer as _Timer\n\nimport yaml as _yaml\n\nfrom gi.repository import GLib\nfrom logitech_receiver.common import NamedInt as _NamedInt\nfrom solaar import __version__\n\n_log = getLogger(__name__)\ndel getLogger\n\n_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))\n_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')\n_yaml_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.yaml')\n\n_KEY_VERSION = '_version'\n_KEY_NAME = '_NAME'\n_KEY_WPID = '_wpid'\n_KEY_SERIAL = '_serial'\n_KEY_MODEL_ID = '_modelId'\n_KEY_UNIT_ID = '_unitId'\n_KEY_ABSENT = '_absent'\n_KEY_SENSITIVE = '_sensitive'\n_config = []\n\n\ndef _load():\n    global _config\n    loaded_config = []\n    if _path.isfile(_yaml_file_path):\n        try:\n            with open(_yaml_file_path) as config_file:\n                loaded_config = _yaml.safe_load(config_file)\n        except Exception as e:\n            _log.error('failed to load from %s: %s', _yaml_file_path, e)\n    elif _path.isfile(_file_path):\n        try:\n            with open(_file_path) as config_file:\n                loaded_config = _json.load(config_file)\n        except Exception as e:\n            _log.error('failed to load from %s: %s', _file_path, e)\n        loaded_config = _convert_json(loaded_config)\n    if _log.isEnabledFor(_DEBUG):\n        _log.debug('load => %s', loaded_config)\n    _config = _cleanup_load(loaded_config)\n\n\nsave_timer = None\nsave_lock = _Lock()\n\n\ndef save(defer=False):\n    global save_timer\n    if not _config:\n        return\n    dirname = _os.path.dirname(_yaml_file_path)\n    if not _path.isdir(dirname):\n        try:\n            _os.makedirs(dirname)\n        except Exception:\n            _log.error('failed to create %s', dirname)\n            return\n    if not defer:\n        do_save()\n    else:\n        with save_lock:\n            if not save_timer:\n                save_timer = _Timer(5.0, lambda: GLib.idle_add(do_save))\n                save_timer.start()\n\n\ndef do_save():\n    global save_timer\n    with save_lock:\n        if save_timer:\n            save_timer.cancel()\n            save_timer = None\n        try:\n            with open(_yaml_file_path, 'w') as config_file:\n                _yaml.dump(_config, config_file, default_flow_style=None, width=150)\n            if _log.isEnabledFor(_INFO):\n                _log.info('saved %s to %s', _config, _yaml_file_path)\n        except Exception as e:\n            _log.error('failed to save to %s: %s', _yaml_file_path, e)\n\n\ndef _convert_json(json_dict):\n    config = [json_dict.get(_KEY_VERSION)]\n    for key, dev in json_dict.items():\n        key = key.split(':')\n        if len(key) == 2:\n            dev[_KEY_WPID] = dev.get(_KEY_WPID) if dev.get(_KEY_WPID) else key[0]\n            dev[_KEY_SERIAL] = dev.get(_KEY_SERIAL) if dev.get(_KEY_SERIAL) else key[1]\n            for k, v in dev.items():\n                if type(k) == str and not k.startswith('_') and type(v) == dict:  # convert string keys to ints\n                    v = {int(dk) if type(dk) == str else dk: dv for dk, dv in v.items()}\n                dev[k] = v\n            for k in ['mouse-gestures', 'dpi-sliding']:\n                v = dev.get(k, None)\n                if v is True or v is False:\n                    dev.pop(k)\n            if '_name' in dev:\n                dev[_KEY_NAME] = dev['_name']\n                dev.pop('_name')\n            config.append(dev)\n    return config\n\n\ndef _cleanup_load(c):\n    _config = [__version__]\n    for element in c:\n        if isinstance(element, dict):\n            divert = element.get('divert-keys')\n            if divert:\n                sliding = element.get('dpi-sliding')\n                if sliding:  # convert old-style dpi-sliding setting to divert-keys entry\n                    divert[int(sliding)] = 3\n                element.pop('dpi-sliding', None)\n                gestures = element.get('mouse-gestures')\n                if gestures:  # convert old-style mouse-gestures setting to divert-keys entry\n                    divert[int(gestures)] = 2\n                element.pop('mouse-gestures', None)\n                # remove any string entries (from bad conversions)\n                element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}\n            # convert to device entries\n            element = _DeviceEntry(**element)\n            _config.append(element)\n    return _config\n\n\nclass _DeviceEntry(dict):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def __setitem__(self, key, value):\n        super().__setitem__(key, value)\n        save(defer=True)\n\n    def update(self, device, modelId):\n        if device.name and device.name != self.get(_KEY_NAME):\n            super().__setitem__(_KEY_NAME, device.name)\n        if device.wpid and device.wpid != self.get(_KEY_WPID):\n            super().__setitem__(_KEY_WPID, device.wpid)\n        if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL):\n            super().__setitem__(_KEY_SERIAL, device.serial)\n        if modelId and modelId != self.get(_KEY_MODEL_ID):\n            super().__setitem__(_KEY_MODEL_ID, modelId)\n        if device.unitId and device.unitId != self.get(_KEY_UNIT_ID):\n            super().__setitem__(_KEY_UNIT_ID, device.unitId)\n\n    def get_sensitivity(self, name):\n        return self.get(_KEY_SENSITIVE, {}).get(name, False)\n\n    def set_sensitivity(self, name, value):\n        sensitives = self.get(_KEY_SENSITIVE, {})\n        if sensitives.get(name) != value:\n            sensitives[name] = value\n            self.__setitem__(_KEY_SENSITIVE, sensitives)\n\n\ndef device_representer(dumper, data):\n    return dumper.represent_mapping('tag:yaml.org,2002:map', data)\n\n\n_yaml.add_representer(_DeviceEntry, device_representer)\n\n\ndef named_int_representer(dumper, data):\n    return dumper.represent_scalar('tag:yaml.org,2002:int', str(int(data)))\n\n\n_yaml.add_representer(_NamedInt, named_int_representer)\n\n\n# A device can be identified by a combination of WPID and serial number (for receiver-connected devices)\n# or a combination of modelId and unitId (for direct-connected devices).\n# But some devices have empty (all zero) modelIds and unitIds.  Use the device name as a backup for the modelId.\n# The worst situation is a receiver-connected device that Solaar has never seen on-line\n# that is directly connected.  Here there is no way to realize that the two devices are the same.\n# So new entries are not created for unseen off-line receiver-connected devices except for those with protocol 1.0\ndef persister(device):\n    def match(wpid, serial, modelId, unitId, c):\n        return ((wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or (\n            modelId and modelId != '000000000000' and modelId == c.get(_KEY_MODEL_ID) and unitId\n            and unitId == c.get(_KEY_UNIT_ID)\n        ))\n\n    if not _config:\n        _load()\n    entry = None\n    modelId = device.modelId if device.modelId != '000000000000' else device.name if device.modelId else None\n    for c in _config:\n        if isinstance(c, _DeviceEntry) and match(device.wpid, device.serial, modelId, device.unitId, c):\n            entry = c\n            break\n    if not entry:\n        if not device.online and not device.serial:  # don't create entry for offline devices without serial number\n            if _log.isEnabledFor(_INFO):\n                _log.info('not setting up persister for offline device %s with missing serial number', device.name)\n            return\n        if _log.isEnabledFor(_INFO):\n            _log.info('setting up persister for device %s', device.name)\n        entry = _DeviceEntry()\n        _config.append(entry)\n    entry.update(device, modelId)\n    return entry\n\n\ndef attach_to(device):\n    pass\n", "path": "lib/solaar/configuration.py"}], "after_files": [{"content": "# -*- python-mode -*-\n\n## Copyright (C) 2012-2013  Daniel Pavel\n##\n## This program is free software; you can redistribute it and/or modify\n## it under the terms of the GNU General Public License as published by\n## the Free Software Foundation; either version 2 of the License, or\n## (at your option) any later version.\n##\n## This program is distributed in the hope that it will be useful,\n## but WITHOUT ANY WARRANTY; without even the implied warranty of\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n## GNU General Public License for more details.\n##\n## You should have received a copy of the GNU General Public License along\n## with this program; if not, write to the Free Software Foundation, Inc.,\n## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nimport json as _json\nimport os as _os\nimport os.path as _path\n\nfrom logging import DEBUG as _DEBUG\nfrom logging import INFO as _INFO\nfrom logging import getLogger\nfrom threading import Lock as _Lock\nfrom threading import Timer as _Timer\n\nimport yaml as _yaml\n\nfrom gi.repository import GLib\nfrom logitech_receiver.common import NamedInt as _NamedInt\nfrom solaar import __version__\n\n_log = getLogger(__name__)\ndel getLogger\n\n_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))\n_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')\n_yaml_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.yaml')\n\n_KEY_VERSION = '_version'\n_KEY_NAME = '_NAME'\n_KEY_WPID = '_wpid'\n_KEY_SERIAL = '_serial'\n_KEY_MODEL_ID = '_modelId'\n_KEY_UNIT_ID = '_unitId'\n_KEY_ABSENT = '_absent'\n_KEY_SENSITIVE = '_sensitive'\n_config = []\n\n\ndef _load():\n    global _config\n    loaded_config = []\n    if _path.isfile(_yaml_file_path):\n        try:\n            with open(_yaml_file_path) as config_file:\n                loaded_config = _yaml.safe_load(config_file)\n        except Exception as e:\n            _log.error('failed to load from %s: %s', _yaml_file_path, e)\n    elif _path.isfile(_file_path):\n        try:\n            with open(_file_path) as config_file:\n                loaded_config = _json.load(config_file)\n        except Exception as e:\n            _log.error('failed to load from %s: %s', _file_path, e)\n        loaded_config = _convert_json(loaded_config)\n    if _log.isEnabledFor(_DEBUG):\n        _log.debug('load => %s', loaded_config)\n    _config = _cleanup_load(loaded_config)\n\n\nsave_timer = None\nsave_lock = _Lock()\n\n\ndef save(defer=False):\n    global save_timer\n    if not _config:\n        return\n    dirname = _os.path.dirname(_yaml_file_path)\n    if not _path.isdir(dirname):\n        try:\n            _os.makedirs(dirname)\n        except Exception:\n            _log.error('failed to create %s', dirname)\n            return\n    if not defer:\n        do_save()\n    else:\n        with save_lock:\n            if not save_timer:\n                save_timer = _Timer(5.0, lambda: GLib.idle_add(do_save))\n                save_timer.start()\n\n\ndef do_save():\n    global save_timer\n    with save_lock:\n        if save_timer:\n            save_timer.cancel()\n            save_timer = None\n        try:\n            with open(_yaml_file_path, 'w') as config_file:\n                _yaml.dump(_config, config_file, default_flow_style=None, width=150)\n            if _log.isEnabledFor(_INFO):\n                _log.info('saved %s to %s', _config, _yaml_file_path)\n        except Exception as e:\n            _log.error('failed to save to %s: %s', _yaml_file_path, e)\n\n\ndef _convert_json(json_dict):\n    config = [json_dict.get(_KEY_VERSION)]\n    for key, dev in json_dict.items():\n        key = key.split(':')\n        if len(key) == 2:\n            dev[_KEY_WPID] = dev.get(_KEY_WPID) if dev.get(_KEY_WPID) else key[0]\n            dev[_KEY_SERIAL] = dev.get(_KEY_SERIAL) if dev.get(_KEY_SERIAL) else key[1]\n            for k, v in dev.items():\n                if type(k) == str and not k.startswith('_') and type(v) == dict:  # convert string keys to ints\n                    v = {int(dk) if type(dk) == str else dk: dv for dk, dv in v.items()}\n                dev[k] = v\n            for k in ['mouse-gestures', 'dpi-sliding']:\n                v = dev.get(k, None)\n                if v is True or v is False:\n                    dev.pop(k)\n            if '_name' in dev:\n                dev[_KEY_NAME] = dev['_name']\n                dev.pop('_name')\n            config.append(dev)\n    return config\n\n\ndef _cleanup_load(c):\n    _config = [__version__]\n    try:\n        for element in c:\n            if isinstance(element, dict):\n                divert = element.get('divert-keys')\n                if divert:\n                    sliding = element.get('dpi-sliding')\n                    if sliding:  # convert old-style dpi-sliding setting to divert-keys entry\n                        divert[int(sliding)] = 3\n                    element.pop('dpi-sliding', None)\n                    gestures = element.get('mouse-gestures')\n                    if gestures:  # convert old-style mouse-gestures setting to divert-keys entry\n                        divert[int(gestures)] = 2\n                    element.pop('mouse-gestures', None)\n                    # remove any string entries (from bad conversions)\n                    element['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)}\n                # convert to device entries\n                element = _DeviceEntry(**element)\n                _config.append(element)\n    except Exception as e:\n        _log.warn('Exception processing config.yaml file, ignoring contents: %s', e)\n    return _config\n\n\nclass _DeviceEntry(dict):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def __setitem__(self, key, value):\n        super().__setitem__(key, value)\n        save(defer=True)\n\n    def update(self, device, modelId):\n        if device.name and device.name != self.get(_KEY_NAME):\n            super().__setitem__(_KEY_NAME, device.name)\n        if device.wpid and device.wpid != self.get(_KEY_WPID):\n            super().__setitem__(_KEY_WPID, device.wpid)\n        if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL):\n            super().__setitem__(_KEY_SERIAL, device.serial)\n        if modelId and modelId != self.get(_KEY_MODEL_ID):\n            super().__setitem__(_KEY_MODEL_ID, modelId)\n        if device.unitId and device.unitId != self.get(_KEY_UNIT_ID):\n            super().__setitem__(_KEY_UNIT_ID, device.unitId)\n\n    def get_sensitivity(self, name):\n        return self.get(_KEY_SENSITIVE, {}).get(name, False)\n\n    def set_sensitivity(self, name, value):\n        sensitives = self.get(_KEY_SENSITIVE, {})\n        if sensitives.get(name) != value:\n            sensitives[name] = value\n            self.__setitem__(_KEY_SENSITIVE, sensitives)\n\n\ndef device_representer(dumper, data):\n    return dumper.represent_mapping('tag:yaml.org,2002:map', data)\n\n\n_yaml.add_representer(_DeviceEntry, device_representer)\n\n\ndef named_int_representer(dumper, data):\n    return dumper.represent_scalar('tag:yaml.org,2002:int', str(int(data)))\n\n\n_yaml.add_representer(_NamedInt, named_int_representer)\n\n\n# A device can be identified by a combination of WPID and serial number (for receiver-connected devices)\n# or a combination of modelId and unitId (for direct-connected devices).\n# But some devices have empty (all zero) modelIds and unitIds.  Use the device name as a backup for the modelId.\n# The worst situation is a receiver-connected device that Solaar has never seen on-line\n# that is directly connected.  Here there is no way to realize that the two devices are the same.\n# So new entries are not created for unseen off-line receiver-connected devices except for those with protocol 1.0\ndef persister(device):\n    def match(wpid, serial, modelId, unitId, c):\n        return ((wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or (\n            modelId and modelId != '000000000000' and modelId == c.get(_KEY_MODEL_ID) and unitId\n            and unitId == c.get(_KEY_UNIT_ID)\n        ))\n\n    if not _config:\n        _load()\n    entry = None\n    modelId = device.modelId if device.modelId != '000000000000' else device.name if device.modelId else None\n    for c in _config:\n        if isinstance(c, _DeviceEntry) and match(device.wpid, device.serial, modelId, device.unitId, c):\n            entry = c\n            break\n    if not entry:\n        if not device.online and not device.serial:  # don't create entry for offline devices without serial number\n            if _log.isEnabledFor(_INFO):\n                _log.info('not setting up persister for offline device %s with missing serial number', device.name)\n            return\n        if _log.isEnabledFor(_INFO):\n            _log.info('setting up persister for device %s', device.name)\n        entry = _DeviceEntry()\n        _config.append(entry)\n    entry.update(device, modelId)\n    return entry\n\n\ndef attach_to(device):\n    pass\n", "path": "lib/solaar/configuration.py"}]}