From 91e62addb7c96f61e1d81eb36e3d00adfc92737e Mon Sep 17 00:00:00 2001 From: June Tate-Gans Date: Tue, 27 Apr 2021 18:05:32 -0500 Subject: [PATCH] g13gui: Define a Preferences class This centralizes the data we manage into a single model that can manage the serialization to/from JSON before it hits disk more effectively. --- g13gui/g13gui/prefs.py | 110 +++++++++++++++++++++++++++++++++++ g13gui/g13gui/prefs_tests.py | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 g13gui/g13gui/prefs.py create mode 100644 g13gui/g13gui/prefs_tests.py diff --git a/g13gui/g13gui/prefs.py b/g13gui/g13gui/prefs.py new file mode 100644 index 0000000..03ae1d3 --- /dev/null +++ b/g13gui/g13gui/prefs.py @@ -0,0 +1,110 @@ +#!/usr/bin/python + +import traceback + +from common import VERSION +from bindingprofile import BindingProfile +from observer import Subject +from observer import ChangeType + + +DEFAULT_PROFILE_NAME = 'Default Profile' + + +class Preferences(Subject): + def __init__(self, dict=None): + self._profiles = {} + self._selectedProfile = None + + if dict: + self.loadFromDict(dict) + else: + self.initDefaultProfile() + + def profiles(self): + return self._profiles + + def profileNames(self): + return sorted(self._profiles.keys()) + + def selectedProfile(self): + return self._profiles[self._selectedProfile] + + def selectedProfileName(self): + return self._selectedProfile + + def initDefaultProfile(self): + self._initDefaultProfile() + self.notifyChanged() + + def addProfile(self, name, profile): + self._addProfile(name, profile) + self.notifyChanged() + + def removeProfile(self, name): + self._removeProfile(name) + self.notifyChanged() + + def setSelectedProfile(self, name): + self._setSelectedProfile(name) + self.notifyChanged() + + def _initDefaultProfile(self): + default_profile = BindingProfile() + self._profiles = {DEFAULT_PROFILE_NAME: default_profile} + self._selectedProfile = DEFAULT_PROFILE_NAME + + self.addChange(ChangeType.ADD, 'profile', {self.selectedProfileName(): self.selectedProfile()}), + self.addChange(ChangeType.MODIFY, 'selectedProfile', self._selectedProfile) + + def _addProfile(self, name, profile): + if name in self._profiles.keys(): + raise KeyError('Profile by name %s is already present' % name) + self._profiles[name] = profile + self.addChange(ChangeType.ADD, 'profile', {name: profile}) + + def _removeProfile(self, name): + del(self._profiles[name]) + self.addChange(ChangeType.REMOVE, 'profile', name) + + if len(self._profiles) == 0: + self.initDefaultProfile() + else: + if self._selectedProfile == name: + self.setSelectedProfile(sorted(self._profiles.keys())[0]) + + def _setSelectedProfile(self, name): + if name not in self._profiles.keys(): + raise KeyError('No profile by name %s present' % name) + self._selectedProfile = name + self.addChange(ChangeType.MODIFY, 'selectedProfile', + self._selectedProfile) + + def saveToDict(self): + return { + 'version': VERSION, + 'profiles': dict([(name, profile.toDict()) for name, profile in self._profiles.items()]), + 'selectedProfile': self._selectedProfile + } + + def loadFromDict(self, dict): + if dict['version'] != VERSION: + print('WARNING: This profile config is from a different version ' + '(wanted %s got %s)!' % (VERSION, dict['version'])) + print('This configuration may not load properly!') + + try: + for name, profile in dict['profiles'].items(): + self._addProfile(name, BindingProfile(profile)) + + self._setSelectedProfile(dict['selectedProfile']) + + except (Exception) as err: + print('Unable to initialize from dict: %s' % err) + print('Continuing with defaults.') + traceback.print_exc() + + self.initDefaultProfile() + + finally: + self.clearChanges() diff --git a/g13gui/g13gui/prefs_tests.py b/g13gui/g13gui/prefs_tests.py new file mode 100644 index 0000000..99b35f9 --- /dev/null +++ b/g13gui/g13gui/prefs_tests.py @@ -0,0 +1,105 @@ +#!/usr/bin/python + +import unittest +import prefs + +from common import VERSION +from observer import ChangeType +from observer import ObserverTestCase + + +class PrefsTestCase(ObserverTestCase): + def setUp(self): + self.prefs = prefs.Preferences() + self.prefs.registerObserver(self) + + def testInitialSetup(self): + self.assertEqual(len(self.prefs.profiles()), 1) + self.assertEqual(len(self.prefs.profileNames()), 1) + self.assertEqual(self.prefs.selectedProfile(), + self.prefs.profiles()[prefs.DEFAULT_PROFILE_NAME]) + self.assertEqual(self.prefs.selectedProfileName(), + self.prefs.profileNames()[0]) + + def testInitialDefaultProfile(self): + self.prefs.initDefaultProfile() + self.assertChangeCount(2) + self.assertChangeNotified(self.prefs, ChangeType.ADD, 'profile') + self.nextChange() + self.assertChangeNotified(self.prefs, ChangeType.MODIFY, + 'selectedProfile') + self.nextChange() + + def testAddRemoveProfile(self): + self.prefs.addProfile('test', {}) + self.assertChangeCount(1) + self.assertChangeNotified(self.prefs, ChangeType.ADD, 'profile') + self.assertChangeDataEquals({'test': {}}) + self.nextChange() + + try: + self.prefs.addProfile('test', {}) + except KeyError: + pass + else: + self.fail('Expected duplicate names to throw KeyError on add.') + + self.prefs.removeProfile('test') + self.assertChangeCount(1) + self.assertChangeNotified(self.prefs, ChangeType.REMOVE, 'profile') + self.assertChangeDataEquals('test') + self.nextChange() + + self.prefs.removeProfile(prefs.DEFAULT_PROFILE_NAME) + self.assertChangeCount(3) + self.assertChangeNotified(self.prefs, ChangeType.REMOVE, 'profile') + self.assertIsNotNone(self.getChangeData()) + self.nextChange() + self.assertChangeNotified(self.prefs, ChangeType.ADD, 'profile') + self.assertIsNotNone(self.getChangeData()) + self.nextChange() + self.assertChangeNotified(self.prefs, ChangeType.MODIFY, + 'selectedProfile') + self.assertChangeDataEquals(prefs.DEFAULT_PROFILE_NAME) + self.nextChange() + + self.assertEqual(len(self.prefs.profiles()), 1) + self.assertEqual(self.prefs.profileNames()[0], + prefs.DEFAULT_PROFILE_NAME) + + def testSetSelectedProfile(self): + self.prefs.addProfile('test', {}) + self.skipChange() + + try: + self.prefs.setSelectedProfile('doesntexist') + except KeyError: + pass + else: + self.fail('Expected setSelectedProfile with a bad profile ' + 'name to raise KeyError') + + self.prefs.setSelectedProfile('test') + self.assertChangeCount(1) + self.assertChangeNotified(self.prefs, ChangeType.MODIFY, + 'selectedProfile') + self.assertChangeDataEquals('test') + self.assertEqual(self.prefs.selectedProfileName(), 'test') + self.assertEqual(self.prefs.selectedProfile(), {}) + + def testSaveLoad(self): + p = prefs.Preferences() + initial_d = p.saveToDict() + self.assertIsNotNone(initial_d) + self.assertEqual(initial_d['version'], VERSION) + self.assertIn('profiles', initial_d) + self.assertIn('selectedProfile', initial_d) + + p = prefs.Preferences(initial_d) + new_d = p.saveToDict() + self.assertEqual(initial_d, new_d) + self.assertChangeCount(0) + + +if __name__ == '__main__': + unittest.main()