Package sabayon :: Module userprofile
[hide private]
[frames] | no frames]

Source Code for Module sabayon.userprofile

  1  # 
  2  # Copyright (C) 2005 Red Hat, Inc. 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU General Public License as published by 
  6  # the Free Software Foundation; either version 2 of the License, or 
  7  # (at your option) any later version. 
  8  # 
  9  # This program is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 12  # GNU General Public License for more details. 
 13  # 
 14  # You should have received a copy of the GNU General Public License 
 15  # along with this program; if not, write to the Free Software 
 16  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 17  # 
 18   
 19  import os 
 20  import os.path 
 21  import sys 
 22  import pygtk; pygtk.require('2.0'); 
 23  import gobject 
 24  from config import * 
 25  import storage 
 26  import traceback 
 27  import util 
 28  import debuglog 
 29   
30 -def dprint (fmt, *args):
31 debuglog.debug_log (False, debuglog.DEBUG_LOG_DOMAIN_USER_PROFILE, fmt % args)
32
33 -class ModuleLoader:
34 """Loads all python modules from a directory allows objects 35 to be constructed from said modules using specific constructors 36 for each object type.""" 37
38 - def __init__ (self, module_path):
39 """Construct a ModuleLoader and will load all available 40 python modules from @module_path. 41 """ 42 self.module_path = module_path 43 self.modules = [] 44 self.__load_modules ()
45
46 - def __load_module (self, module):
47 """Load a python module named @module.""" 48 dprint ("Loading module: %s", module) 49 cmd = "from sources import %s" % module 50 try: 51 exec (cmd) 52 except: 53 print >> sys.stderr, "Failed to import module '%s' cmd=\"%s\"" % (module, cmd) 54 traceback.print_exc(file=sys.stderr) 55 return 56 self.modules.append (module)
57
58 - def __load_modules (self):
59 """Load all available modules from self.module_path.""" 60 dprint ("Loading modules from %s", self.module_path) 61 sys.path.append (self.module_path) 62 for file in os.listdir (self.module_path): 63 if file[0] == '.': 64 continue 65 if file[-3:] != ".py": 66 continue 67 if file == "__init__.py": 68 continue 69 self.__load_module (file[:-3])
70
71 - def __construct_object (self, module, constructor, arg):
72 """Construct an object by invoking a function named @constructor, 73 with @arg as an argument, in the module called @module. 74 """ 75 dprint ("Constructing object from loaded module using %s.%s", module, constructor) 76 cmd = ("from sources import %s\n" 77 "if %s.__dict__.has_key ('%s'):" 78 " ret = %s.%s (arg)") % (module, module, constructor, module, constructor) 79 try: 80 exec (cmd) 81 except: 82 print "Failed to invoke function '%s.%s': %s" % (module, constructor, sys.exc_type) 83 traceback.print_exc(file=sys.stderr) 84 return None 85 86 try: 87 ret = ret 88 except NameError: 89 return None 90 return ret
91
92 - def construct_objects (self, constructor, arg):
93 """Construct and return a list of objects by invoking a function 94 named @constructor, with @arg as an argument, in each of the 95 python modules in self.module_path which contain a function 96 by that name. 97 """ 98 objects = [] 99 for module in self.modules: 100 o = self.__construct_object (module, constructor, arg) 101 if o != None: 102 objects.append (o) 103 return objects
104 105 module_loader = None
106 -def get_module_loader ():
107 """Return a singleton ModuleLoader object.""" 108 global module_loader 109 if module_loader == None: 110 module_loader = ModuleLoader (os.path.join (os.path.dirname (__file__), "sources")) 111 return module_loader
112
113 -class ProfileChange (gobject.GObject):
114 """Abstract base class for encapsulating profile changes.""" 115
116 - def __init__ (self, source, delegate = None):
117 """Construct a ProfileChange object. 118 119 source: the ProfileSource from which the change came. 120 """ 121 gobject.GObject.__init__ (self) 122 self.source = source 123 self.delegate = delegate
124
125 - def get_source (self):
126 """Get the ProfileSource from which this change came.""" 127 return self.source
128
129 - def get_id (self):
130 """Return and identifier for the configuration item which changed.""" 131 raise Exception ("Not implemented")
132
133 - def get_short_description (self):
134 """Return a short description of the configuration change.""" 135 raise Exception ("Not implemented")
136
137 - def get_ignore_default (self):
138 """Returns the default value for the ignore item for this change.""" 139 return False;
140
141 - def get_mandatory (self):
142 """Returns the mandatory value of the change, or None to use the default""" 143 return None
144
145 - def merge_old_change (self, old_change):
146 """Merges info from previous change into this change. Return True to discard the new change.""" 147 return False
148 149 gobject.type_register (ProfileChange) 150
151 -class SourceDelegate:
152 """An abstract base class for helper classes which can be used 153 to intercept and modify changes from a given configuration 154 source.""" 155
156 - def __init__ (self, delegate_name, source, namespace_section):
157 """Construct a SourceDelegate object. 158 159 @source: the ProfileSource whose changes the delegate wishes 160 to inspect. 161 @namepsace_section: the section of @source's configuration 162 namespace that the delegate wishes to inspect. 163 """ 164 self.name = delegate_name 165 self.source = source 166 self.namespace_section = namespace_section
167
168 - def get_name (self):
169 """Returns the configuration delegate's name.""" 170 return self.name
171
172 - def get_path_description (self, path):
173 """Returns a human readable description for @path. 174 @path must have been previously added by the delegate to the 175 #ProfileStorage. 176 """ 177 return path
178
179 - def handle_change (self, change):
180 """Inspect a ProfileChange. Return #True if the change should 181 not be passed on any further (i.e. #True == 'handled') and 182 return #False if the change should be passed on unmodified. 183 """ 184 raise Exception ("Not implemented")
185
186 - def commit_change (self, change, mandatory = False):
187 """Commit a change to profile. 188 189 mandatory: whether the change should be committed such 190 that it overrides the user's value. 191 """ 192 raise Exception ("Not implemented")
193
194 - def start_monitoring (self):
195 """Start monitoring for configuration changes.""" 196 raise Exception ("Not implemented")
197
198 - def stop_monitoring (self):
199 """Stop monitoring for configuration changes.""" 200 raise Exception ("Not implemented")
201
202 - def sync_changes (self):
203 """Save all committed changes to disk.""" 204 raise Exception ("Not implemented")
205
206 - def set_enforce_mandatory (self, enforce):
207 """Temporary make mandatory setting non-mandatory.""" 208 raise Exception ("Not implemented")
209
210 - def apply (self, is_sabayon_session):
211 """Apply profile to the current user's environment.""" 212 raise Exception ("Not implemented")
213
214 -class ProfileSource (gobject.GObject):
215 """An abstract base class which each configuration source must 216 implement.""" 217 218 __gsignals__ = { 219 "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (ProfileChange, )) 220 } 221
222 - def __init__ (self, source_name, delegate_constructor = None):
223 """Construct a ProfileSource object.""" 224 gobject.GObject.__init__ (self) 225 self.name = source_name 226 227 module_loader = get_module_loader () 228 229 self.delegates = [] 230 if delegate_constructor: 231 self.delegates = module_loader.construct_objects (delegate_constructor, self) 232 # Set the priority level for sorting in Details view, lower numbers appear first 233 self.SORTPRIORITY = 1000
234
235 - def get_delegate (self, delegate_name):
236 """Return a delegate named @delegate_name.""" 237 for delegate in self.delegates: 238 if delegate.get_name () == delegate_name: 239 return delegate 240 return None
241
242 - def get_name (self):
243 """Returns the configuration source's name.""" 244 return self.name
245
246 - def get_path_description (self, path):
247 """Returns a human readable description for @path. 248 @path must have been previously added by the source to the 249 #ProfileStorage. 250 """ 251 return path
252
253 - def emit_change (self, change):
254 """Pass @change to all register delegates for this source and 255 emit a 'changed' signal with @change if none of the delegates 256 return #True. 257 """ 258 for delegate in self.delegates: 259 if not change.get_id ().startswith (delegate.namespace_section): 260 continue 261 if delegate.handle_change (change): 262 dprint ("Delegate '%s' handled change '%s'", delegate.get_name (), change.get_id ()) 263 return 264 self.emit ("changed", change)
265
266 - def commit_change (self, change, mandatory = False):
267 """Commit a change to profile. 268 269 mandatory: whether the change should be committed such 270 that it overrides the user's value. 271 """ 272 if change.delegate: 273 change.delegate.commit_change (change, mandatory) 274 dprint ("Delegate '%s' committed changes '%s'", change.delegate.get_name (), change.get_id ()) 275 return True 276 return False
277
278 - def start_monitoring (self):
279 """Start monitoring for configuration changes.""" 280 raise Exception ("Not implemented")
281
282 - def stop_monitoring (self):
283 """Stop monitoring for configuration changes.""" 284 raise Exception ("Not implemented")
285
286 - def sync_changes (self):
287 """Save all committed changes to disk.""" 288 raise Exception ("Not implemented")
289
290 - def set_enforce_mandatory (self, enforce):
291 """Temporary make mandatory setting non-mandatory.""" 292 raise Exception ("Not implemented")
293
294 - def apply (self, is_sabayon_session):
295 """Apply profile to the current user's environment.""" 296 raise Exception ("Not implemented")
297 298 gobject.type_register (ProfileSource) 299
300 -class UserProfile (gobject.GObject):
301 """An encapsulation of the user profile and backend sources.""" 302 303 __gsignals__ = { 304 "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (ProfileChange, )) 305 } 306
307 - def __init__ (self, profile_name):
308 """Construct a UserProfile 309 310 profile_name: name of the profile to be loaded 311 module_path: optional path from which configuration modules 312 should be loaded 313 """ 314 gobject.GObject.__init__ (self) 315 316 dprint ("Constructing profile from %s", profile_name) 317 318 self.profile_name = profile_name 319 320 # 321 # Open the user settings packages and try to install them 322 # 323 self.storage = storage.ProfileStorage (profile_name) 324 325 module_loader = get_module_loader () 326 327 self.sources = [] 328 self.sources = module_loader.construct_objects ("get_source", self.storage) 329 dprint ("%d sources loaded:", len (self.sources)) 330 for source in self.sources: 331 dprint (" %s", source.get_name ()) 332 source.connect ("changed", self.__handle_source_changed)
333
334 - def __handle_source_changed (self, source, change):
335 self.emit ("changed", change)
336
337 - def get_source (self, source_name):
338 """Return a source named @source_name.""" 339 for source in self.sources: 340 if source.get_name () == source_name: 341 return source 342 return None
343
344 - def get_delegate (self, delegate_name):
345 """Return a delegate named @delegate_name.""" 346 for source in self.sources: 347 delegate = source.get_delegate (delegate_name) 348 if not delegate is None: 349 return delegate 350 return None
351
352 - def start_monitoring (self):
353 """Start monitoring for configuration changes.""" 354 dprint ("Starting monitoring") 355 for s in self.sources: 356 # Start source monitor before delegates so we 357 # can e.g. set up gconf in the gconf source and 358 # use it in the gconf delegate 359 s.start_monitoring () 360 for delegate in s.delegates: 361 delegate.start_monitoring ()
362
363 - def stop_monitoring (self):
364 """Stop monitoring for configuration changes.""" 365 dprint ("Stopping monitoring") 366 for s in self.sources: 367 for delegate in s.delegates: 368 delegate.stop_monitoring () 369 s.stop_monitoring ()
370
371 - def sync_changes (self):
372 """Save all committed changes to disk.""" 373 dprint ("Syncing all committed changes to disk") 374 for s in self.sources: 375 for delegate in s.delegates: 376 delegate.sync_changes () 377 s.sync_changes () 378 self.storage.save ()
379
380 - def set_enforce_mandatory (self, enforce):
381 """Temporary make mandatory setting non-mandatory.""" 382 dprint ("Changing mandatory enforcement in user's environment") 383 for s in self.sources: 384 s.set_enforce_mandatory (enforce) 385 for delegate in s.delegates: 386 delegate.set_enforce_mandatory (enforce)
387
388 - def apply (self, is_sabayon_session):
389 """Apply profile to the current user's environment.""" 390 dprint ("Applying profile to user's environment") 391 for s in self.sources: 392 s.apply (is_sabayon_session) 393 for delegate in s.delegates: 394 delegate.apply (is_sabayon_session)
395 396 gobject.type_register (UserProfile) 397 398 # 399 # Unit tests 400 #
401 -def run_unit_tests ():
402 class LocalTestChange (ProfileChange): 403 def __init__ (self, source, key, value): 404 ProfileChange.__init__ (self, source) 405 self.key = key 406 self.value = value
407 def get_id (self): 408 return self.key 409 def get_short_description (self): 410 return self.key 411 412 class LocalTestDelegate (SourceDelegate): 413 def __init__ (self, source): 414 SourceDelegate.__init__ (self, "local", source, "/bar") 415 def handle_change (self, change): 416 if change.get_id () == "/bar/foo1": 417 return True 418 return False 419 420 class LocalTestSource (ProfileSource): 421 def __init__ (self): 422 ProfileSource.__init__ (self, "local") 423 424 profile = UserProfile ("foo-storage") 425 assert len (profile.sources) > 0 426 427 testsource = None 428 for source in profile.sources: 429 if source.get_name () == "test": 430 testsource = source 431 break; 432 assert testsource != None 433 assert len (testsource.delegates) == 1 434 435 localsource = LocalTestSource () 436 profile.sources.append (localsource) 437 def handle_source_changed (source, change, profile): 438 profile.emit ("changed", change) 439 localsource.connect ("changed", handle_source_changed, profile) 440 assert len (profile.sources) > 1 441 442 localdelegate = LocalTestDelegate (localsource) 443 localsource.delegates.append (localdelegate) 444 445 global n_signals 446 n_signals = 0 447 def handle_changed (profile, change): 448 global n_signals 449 n_signals += 1 450 profile.connect ("changed", handle_changed) 451 452 # Only foo2 and foo3 should get through 453 localsource.emit_change (LocalTestChange (localsource, "/bar/foo1", "1")) 454 localsource.emit_change (LocalTestChange (localsource, "/bar/foo2", "2")) 455 localsource.emit_change (LocalTestChange (localsource, "/bar/foo3", "3")) 456 457 # Only bar2 and bar3 should get through 458 testsource.emit_change (testsource.get_test_change ("/foo/bar1", "1")) 459 testsource.emit_change (testsource.get_test_change ("/foo/bar2", "2")) 460 testsource.emit_change (testsource.get_test_change ("/foo/bar3", "3")) 461 462 assert n_signals == 4 463