persistence.py
1 #!/usr/bin/env python 2 # vim: sts=4 sw=4 et 3 # This is a component of EMC 4 # util.py Copyright 2010 Michael Haberler 5 # 6 # 7 # This program is free software; you can redistribute it and/or modify 8 # it under the terms of the GNU General Public License as published by 9 # the Free Software Foundation; either version 2 of the License, or 10 # (at your option) any later version. 11 # 12 # This program is distributed in the hope that it will be useful, 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # GNU General Public License for more details. 16 # 17 # You should have received a copy of the GNU General Public License 18 # along with this program; if not, write to the Free Software 19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.''' 20 ''' 21 persistence support for gladevcp widgets 22 Michael Haberler 11/2010 23 ''' 24 import os 25 import sys 26 import time 27 import gtk 28 from configobj import ConfigObj, flatten_errors 29 from validate import Validator 30 from hashlib import sha1 31 from hal_widgets import _HalWidgetBase 32 from hal_actions import _EMC_ActionBase 33 from gladevcp.gladebuilder import widget_name 34 35 class UselessIniError(Exception): 36 pass 37 38 class BadDescriptorDictError(Exception): 39 pass 40 41 # map python type names to ConfigObj type names 42 co_map = {'str': 'string','int':'integer', 'str': 'string', 'bool': 'boolean'} 43 debug = 0 44 version_number = 1 45 46 47 def warn(*args): 48 print >> sys.stderr,''.join(args) 49 50 51 def dbg(level,*args): 52 global debug 53 if debug < level: return 54 print ''.join(args) 55 56 57 def set_debug(value): 58 global debug 59 debug = value 60 61 62 def select_widgets(widgets, hal_only=False,output_only = False): 63 ''' 64 input: a list of widget instances 65 output: a list of widget instances, pruned according to the flags: 66 hal_only = True: only add HAL widgets which make sense restoring 67 output_only = True: only add widgets which let users 68 set a value (scale,button..) 69 ''' 70 wlist = [] 71 for w in widgets: 72 # always skip the following types: 73 if isinstance(w, (_EMC_ActionBase)): 74 continue 75 if hal_only and not isinstance(w, _HalWidgetBase): 76 continue 77 if output_only and not isinstance(w, (gtk.Range, 78 gtk.SpinButton, 79 gtk.ComboBox, 80 gtk.CheckButton, 81 gtk.ToggleButton, 82 gtk.RadioButton)): 83 continue 84 wlist.append(w) 85 return wlist 86 87 88 def accessors(w): 89 ''' 90 retrieve getter/setter name of an 'interesting' widget 91 ''' 92 if isinstance(w, (gtk.Range, gtk.SpinButton)): 93 return (w.get_value,w.set_value) 94 if isinstance(w, (gtk.CheckButton, gtk.ToggleButton,gtk.RadioButton,gtk.ComboBox)): 95 return (w.get_active,w.set_active) 96 return (None,None) 97 98 def store_value(widget,value): 99 _,setter = accessors(widget) 100 return setter and setter(value) 101 102 def get_value(widget): 103 getter,_ = accessors(widget) 104 return getter and getter() 105 106 def widget_defaults(widgets): 107 ''' 108 return a dict name/current value of wvalues which are to be restored 109 ''' 110 wvalues = dict() 111 for w in widgets: 112 k = widget_name(w) 113 try: 114 v = get_value(w) 115 wvalues[k] = v 116 except Exception,msg: 117 warn("widget_defaults:" + msg) 118 continue 119 return wvalues 120 121 122 class IniFile(object): 123 124 # well known section and variable names in .ini files 125 vars = 'vars' 126 widgets = 'widgets' 127 ini = 'ini' 128 signature = 'signature' 129 version = 'version' 130 131 def _gen_spec(self,vdict): 132 ''' 133 given a nested dict of sections,vars and default values, 134 derive a ConfigObj typed spec 135 NB: only single level nesting supported 136 137 FIXME: add safeguards against broken vdict() 138 ''' 139 spec = '' 140 for section in sorted(vdict.keys()): 141 spec += '[' + section + ']\n' 142 for varname in sorted(vdict[section].keys()): 143 typename = type(vdict[section][varname]).__name__ 144 if co_map.has_key(typename): 145 typename = co_map[typename] 146 spec += '\t' + varname + ' = ' + typename + '\n' 147 return spec 148 149 def restore_state(self,obj): 150 ''' 151 restore attributes from ini file 'vars' section as obj attributes, 152 as well as any widget state in 'widgets' section 153 ''' 154 dbg(1, "restore_state() from %s" % (self.filename)) 155 156 if not self.defaults.has_key(IniFile.ini): 157 raise BadDescriptorDictError("defaults dict lacks 'ini' section") 158 159 if self.defaults[IniFile.ini][IniFile.signature] != ( 160 self.config[IniFile.ini][IniFile.signature]): 161 warn("signature mismatch in %s - resetting to default" % 162 (self.filename)) 163 dbg(1, "expected: %s, got %s" % 164 (self.defaults[IniFile.ini][IniFile.signature], 165 self.config[IniFile.ini][IniFile.signature])) 166 self.create_default_ini() 167 else: 168 dbg(1,"signature verified OK for %s " % (self.filename)) 169 170 if self.config.has_key(IniFile.vars): 171 for k,v in self.defaults[IniFile.vars].items(): 172 setattr(obj,k,self.config[IniFile.vars][k]) 173 174 if self.config.has_key(IniFile.widgets): 175 for k,v in self.config[IniFile.widgets].items(): 176 store_value(self.builder.get_object(k),v) 177 178 def save_state(self, obj): 179 ''' 180 save obj attributes as listed in ini file 'IniFile.vars' section and 181 widget state to 'widgets' section 182 ''' 183 if self.defaults.has_key(IniFile.vars): 184 for k,v in self.defaults[IniFile.vars].items(): 185 self.config[IniFile.vars][k] = getattr(obj,k,None) 186 187 if self.config.has_key(IniFile.widgets): 188 for k in self.defaults[IniFile.widgets].keys(): 189 self.config[IniFile.widgets][k] = get_value(self.builder.get_object(k)) 190 191 self.config.final_comment = ['last update by %s.save_state() on %s ' % 192 (__name__,time.asctime())] 193 self.write() 194 dbg(1, "save_state() to %s" % (self.filename)) 195 196 197 def create_default_ini(self): 198 ''' 199 create a default ini file with defaults derived from configspec 200 ''' 201 self.config = ConfigObj(self.filename, interpolation=False, 202 configspec=self.spec) 203 vdt = Validator() 204 self.config.validate(vdt, copy=True) 205 self.config.update(self.defaults) 206 self.config.filename = self.filename 207 self.config.initial_comment.append('generated by %s.create_default_ini() on %s' % 208 (__name__, time.asctime())) 209 self.config.write() 210 211 def read_ini(self): 212 ''' 213 make sure current file validates OK, this will also type-convert values 214 recreate default ini file if bad things happen 215 ''' 216 retries = 2 217 while True: 218 try: 219 self.config = ConfigObj(self.filename, 220 file_error=True, 221 interpolation=False, 222 configspec=self.spec, 223 raise_errors=True) 224 validator = Validator() 225 results = self.config.validate(validator,preserve_errors=True) 226 if results != True: 227 for (section_list, key, error 228 ) in flatten_errors(self.config, results): 229 if key is not None: 230 warn("key '%s' in section '%s' : %s" % 231 (key, ', '.join(section_list), 232 error if error else "missing")) 233 else: 234 warn("section '%s' missing" % 235 ', '.join(section_list)) 236 badfilename = self.filename + '.BAD' 237 os.rename(self.filename,badfilename) 238 retries -= 1 239 if retries: 240 raise UselessIniError("cant make sense of '%s', renamed to '%s'" 241 % (self.filename, badfilename)) 242 else: 243 raise Exception(error) 244 245 except (IOError, TypeError,UselessIniError),msg: 246 warn("%s - creating default" % (msg)) 247 self.create_default_ini() 248 continue 249 250 except: # pass on ConfigObj's other troubles 251 raise 252 253 else: # all great 254 break 255 256 def write(self,filename=None): 257 ''' 258 write out changed config 259 ''' 260 if filename: 261 self.filename = filename 262 self.config.write() 263 264 def __init__(self,filename,defaults,builder): 265 defaults[IniFile.ini] = { IniFile.signature : 'astring', 266 IniFile.version : version_number } 267 self.defaults = defaults 268 self.filename = filename 269 self.builder = builder 270 spec = self._gen_spec(self.defaults) 271 self.signature = sha1(spec).hexdigest() 272 self.defaults[IniFile.ini][IniFile.signature] = self.signature 273 274 dbg(2, "auto-generated spec:\n%s\nsignature = %s" % 275 (spec, self.signature)) 276 self.spec = spec.splitlines() 277 self.read_ini()