PageRenderTime 110ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/tv/lib/config.py

https://github.com/kazcw/miro
Python | 240 lines | 179 code | 12 blank | 49 comment | 5 complexity | 3d0eadc7ca4f5d26d3bade1849f97212 MD5 | raw file
  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
  3. # Participatory Culture Foundation
  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
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. #
  19. # In addition, as a special exception, the copyright holders give
  20. # permission to link the code of portions of this program with the OpenSSL
  21. # library.
  22. #
  23. # You must obey the GNU General Public License in all respects for all of
  24. # the code used other than OpenSSL. If you modify file(s) with this
  25. # exception, you may extend this exception to your version of the file(s),
  26. # but you are not obligated to do so. If you do not wish to do so, delete
  27. # this exception statement from your version. If you delete this exception
  28. # statement from all source files in the program, then also delete it here.
  29. """``miro.config`` -- Configuration and preference related functions.
  30. """
  31. import functools
  32. import logging
  33. from threading import RLock
  34. from miro.appconfig import AppConfig
  35. from miro import app
  36. from miro import prefs
  37. from miro import signals
  38. from miro.plat import config as platformcfg
  39. def _with_lock(func):
  40. """Wrapper function that uses a lock to synchronize access."""
  41. def lock_wrapper(self, *args, **kwargs):
  42. self._lock.acquire()
  43. try:
  44. func(self, *args, **kwargs)
  45. finally:
  46. self._lock.release()
  47. return functools.update_wrapper(lock_wrapper, func)
  48. class ConfigurationBase(object):
  49. """Base class for Configuration handling
  50. """
  51. def __init__(self):
  52. self._data = None
  53. self._lock = RLock()
  54. self._watchers = set()
  55. # Load the preferences
  56. self._data = platformcfg.load()
  57. if self._data is None:
  58. self._data = dict()
  59. @_with_lock
  60. def _add_watcher(self, watcher):
  61. """Add a ConfigWatcher to our internal list.
  62. """
  63. self._watchers.add(watcher)
  64. @_with_lock
  65. def _remove_watcher(self, watcher):
  66. """Remove a ConfigWatcher from our list.
  67. """
  68. self._watchers.discard(watcher)
  69. @_with_lock
  70. def get(self, descriptor):
  71. return self._data[descriptor.key]
  72. def get_platform_default(self, descriptor):
  73. """Get the platform-specific default value for a preference.
  74. For platform-specific preferences, we can't set the default attribute
  75. since it will be different for each platform and also could depend on
  76. things like the home directory.
  77. Instead, use this method to get the value from the miro.plat.config
  78. module.
  79. """
  80. return platformcfg.get(descriptor)
  81. @_with_lock
  82. def set(self, descriptor, value):
  83. self.set_key(descriptor.key, value)
  84. @_with_lock
  85. def set_key(self, key, value):
  86. logging.debug("Setting %s to %s", key, value)
  87. if (key not in self._data or
  88. self._data[key] != value):
  89. self._data[key] = value
  90. self._emit_on_watchers("changed", key, value)
  91. def _emit_on_watchers(self, signal, *args):
  92. for watcher in self._watchers:
  93. watcher.emit(signal, *args)
  94. class Configuration(ConfigurationBase):
  95. """Configuration class used in the main process
  96. config.load() sets up the global Configuration object using the app.config
  97. variable.
  98. """
  99. def __init__(self):
  100. ConfigurationBase.__init__(self)
  101. # This is a bit of a hack to automagically get the serial
  102. # number for this platform
  103. prefs.APP_SERIAL.key = 'appSerial-%s' % self.get(prefs.APP_PLATFORM)
  104. def get(self, descriptor, use_theme_data=True):
  105. if descriptor.key in self._data:
  106. value = self._data[descriptor.key]
  107. if ((descriptor.possible_values is not None
  108. and not value in descriptor.possible_values)):
  109. logging.warn(
  110. 'bad preference value %s for key %s. using failsafe: %s',
  111. value, descriptor.key, descriptor.failsafe_value)
  112. return descriptor.failsafe_value
  113. else:
  114. return value
  115. elif descriptor.platformSpecific:
  116. return self.get_platform_default(descriptor)
  117. if app.configfile.contains(descriptor.key, use_theme_data):
  118. return app.configfile.get(descriptor.key, use_theme_data)
  119. else:
  120. return descriptor.default
  121. @_with_lock
  122. def save(self):
  123. platformcfg.save(self._data)
  124. class ManualConfig(ConfigurationBase):
  125. """Configuration class where we set the values manually.
  126. This is used in the downloader process and child process from
  127. subprocessmanager.
  128. """
  129. def __init__(self):
  130. ConfigurationBase.__init__(self)
  131. self._initial_config_loaded = False
  132. def set_dictionary(self, data):
  133. if self._initial_config_loaded:
  134. raise AssertionError("set dictionary called twice")
  135. self._data = data
  136. self._initial_config_loaded = True
  137. def get(self, pref):
  138. if pref.key in self._data:
  139. return self._data[pref.key]
  140. elif pref.platformSpecific:
  141. return self.get_platform_default(pref)
  142. else:
  143. return pref.default
  144. class TemporaryConfiguration(Configuration):
  145. """Configuration class for the unit tests"""
  146. def __init__(self):
  147. Configuration.__init__(self)
  148. # make _data a plain dict. On linux, we don't want this to be a
  149. # GConfDict, which auto-saves changes
  150. self._data = {}
  151. def save(self):
  152. pass
  153. class ConfigWatcher(signals.SignalEmitter):
  154. """Emits signals when the config changes.
  155. Config changes can happen in any thread. This class allows us to have
  156. signals handlers always execute in the same thread.
  157. signals:
  158. changed(key, value) : a config value changed
  159. """
  160. def __init__(self, thread_caller):
  161. """Construct a ConfigWatcher
  162. This class needs a function that can call another function in a
  163. specific thread. It should input (func, *args), and call func with
  164. args on the thread.
  165. Typical Example:
  166. def thread_caller(func, *args):
  167. eventloop.add_idle(func, "config callback", args=args)
  168. app.backend_config_watcher = ConfigWatcher(thread_caller)
  169. :args thread_caller: function that invoke another function in a
  170. specific thread.
  171. """
  172. signals.SignalEmitter.__init__(self, 'changed')
  173. self.thread_caller = thread_caller
  174. app.config._add_watcher(self)
  175. def emit(self, signal, *args):
  176. self.thread_caller(signals.SignalEmitter.emit,
  177. self, signal, *args)
  178. def destroy(self):
  179. app.config._remove_watcher(self)
  180. def load(config_obj=None):
  181. if app.config is not None:
  182. raise AssertionError("Config already loaded")
  183. app.configfile = AppConfig(None)
  184. if config_obj is None:
  185. app.config = Configuration()
  186. else:
  187. app.config = config_obj
  188. def load_temporary():
  189. """This initializes temporary mode where all configuration
  190. set calls are temporary.
  191. """
  192. app.configfile = AppConfig(None)
  193. app.config = TemporaryConfiguration()
  194. def set_theme(theme):
  195. """Setup the theme to get config data from.
  196. This method exists because we need to create the config object ASAP,
  197. before we know the theme on some platforms. Therfore, we create the
  198. config object, then later on set the theme.
  199. """
  200. app.configfile = AppConfig(theme)