Package elisa :: Package core :: Package utils :: Module update_checker

Source Code for Module elisa.core.utils.update_checker

  1  # -*- coding: utf-8 -*- 
  2  # Moovida - Home multimedia server 
  3  # Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Moovida with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Moovida is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Moovida" in the root directory of this distribution package 
 15  # for details on that license. 
 16  # 
 17  # Authors: Benjamin Kampmann <benjamin@fluendo.com> 
 18  #          Olivier Tilloy <olivier@fluendo.com> 
 19   
 20  """ 
 21  Checks for updates on the internet 
 22  """ 
 23   
 24  from urllib import quote 
 25   
 26  from elisa.core import common 
 27  from elisa.core.utils import defer, misc 
 28  from elisa.core.utils.internet import get_page 
 29  from elisa.core.log import Loggable 
 30  from elisa.core.plugin import Plugin 
 31  from elisa.core.components.message import Message 
 32   
 33  from twisted.internet import reactor, error, task 
 34   
 35  from distutils.version import LooseVersion 
 36   
 37   
 38  """ 
 39  the url used for lookup 
 40  """ 
 41  HOST = "www.moovida.com" 
 42  UPDATES_URL = "http://" + HOST + "/updates/updates/?" \ 
 43                "install_date=%(date)s&version=%(version)s&os=%(os)s" \ 
 44                "&user_id=%(user_id)s&aen=%(aen)s&entity_id=%(entity_id)s" \ 
 45                "&referrer=%(referrer)s&traffic_unit=%(traffic_unit)s" 
 46   
 47   
48 -class AlreadyRunning(Exception):
49 pass
50 51
52 -class AvailablePluginUpdatesMessage(Message):
53 54 """ 55 Message sent when plugins updates and/or new recommended plugins are 56 available for download from the plugin repository. 57 58 @ivar available_updates: the list of plugins available for update 59 @type available_updates: C{list} of C{dict} 60 @ivar new_recommended: the list of new recommended plugins available 61 @type new_recommended: C{list} of C{dict} 62 """ 63
64 - def __init__(self, available_updates, new_recommended):
65 super(AvailablePluginUpdatesMessage, self).__init__() 66 self.available_updates = available_updates 67 self.new_recommended = new_recommended
68 69
70 -class UpdateChecker(Loggable):
71 72 """ 73 Helper Class for simple look up of updates on the remote elisa server. 74 """ 75 76 # check each day, the value is in seconds 77 check_interval = 86400 78
79 - def __init__(self, install_date, user_id, version, **extra_affiliation_params):
80 super(UpdateChecker, self).__init__() 81 82 # initial data 83 self.install_date = install_date 84 self.user_id = user_id 85 self.version = version 86 self.operating_system = misc.get_os_name() 87 self.extra_affiliation_params = extra_affiliation_params 88 89 self._pending_call = None 90 self._current_dfr = None
91
92 - def parse_result(self, result):
93 """ 94 Parse the given data into a dictionary. The syntax for result has to be 95 a key-value pair per line separated by a colon (':'). Spaces at the 96 beginning or the end are stripped from both the key and the value. 97 For instance:: 98 99 foo: bar 100 test: partial 101 maybe : maybe not 102 103 @param result: the result to parse 104 @type result: C{str} 105 106 @return: a dictionary containing the key-value pairs 107 @rtype: C{dict} 108 """ 109 result_dict = {} 110 for line in result.splitlines(): 111 try: 112 key, value = line.split(':', 1) 113 except ValueError: 114 self.warning("Could not split '%s'" % line) 115 continue 116 117 result_dict[key.strip()] = value.strip() 118 119 return result_dict
120
121 - def _get(self, uri):
122 # This method is meant to be monkey-patched in unit tests. 123 self.info("Retrieving page at %s", uri) 124 return get_page(uri)
125
126 - def request(self):
127 """ 128 Request for update data and parse it 129 """ 130 request_params = {'user_id': quote(self.user_id), 131 'version': quote(self.version), 132 'date': quote(self.install_date), 133 'os': quote(self.operating_system)} 134 request_params.update(self.extra_affiliation_params) 135 136 request_url = UPDATES_URL % request_params 137 138 dfr = self._get(request_url) 139 dfr.addCallback(self.parse_result) 140 return dfr
141
142 - def start(self, callback):
143 """ 144 Sets up an automatic loop of update url calls starting right now. 145 Everytime a result is received the callback is triggered with a 146 dictionary of the parsed result as argument. The next iteration is done 147 every L{check_interval}-seconds. 148 149 @raises AlreadyRunning: if the method was already called before. It is only 150 allowed to call this method once. It is mandatory to call L{stop} before 151 calling start again. 152 """ 153 if self._pending_call is not None or self._current_dfr is not None: 154 raise AlreadyRunning("Don't call me twice") 155 156 self._auto_request(callback)
157
158 - def _got_response(self, result, callback):
159 if not result: 160 self.warning("response of the Server was empty!") 161 return 162 163 dfr = callback(result) 164 return dfr
165
166 - def _response_failed(self, failure):
167 if failure.type != defer.CancelledError: 168 self.warning("Problem when requesting for" \ 169 "update: %s" % failure) 170 171 # in any case, eat the error 172 return None
173
174 - def _update_plugin_cache(self, result):
175 plugin_registry = common.application.plugin_registry 176 177 def get_updates_and_new_recommended_list(cache_file): 178 plugins = \ 179 plugin_registry.get_downloadable_plugins(reload_cache=True) 180 installed = list(plugin_registry.get_plugin_names()) 181 # hack for https://bugs.launchpad.net/elisa/+bug/341172 182 if 'elisa-plugin-amp' not in installed: 183 installed.append('elisa-plugin-amp') 184 available_updates = [] 185 new_recommended = [] 186 187 def get_current_plugin_version(plugin_name): 188 current = plugin_registry.get_plugin_by_name(plugin_name) 189 if current is not None: 190 return LooseVersion(current.version) 191 192 if plugin_name == 'elisa-plugin-amp': 193 # case of #341172: the plugin_registry didn't find amp, 194 # even though it is *always* installed, in that case, we 195 # know we have version 0.1, which didn't have its egg-info. 196 return LooseVersion('0.1')
197 198 def iterate_plugins(plugins, installed, 199 available_updates, new_recommended): 200 for plugin_dict in plugins: 201 plugin = Plugin.from_dict(plugin_dict) 202 if plugin.name not in installed and \ 203 plugin_dict['quality'] == 'recommended' and \ 204 plugin.runs_on_current_platform(): 205 new_recommended.append(plugin_dict) 206 elif plugin.name in installed: 207 current_version = \ 208 get_current_plugin_version(plugin.name) 209 if plugin.version > current_version: 210 available_updates.append(plugin_dict) 211 yield None
212 213 def send_message(result, available_updates, new_recommended): 214 if len(available_updates) > 0 or len(new_recommended) > 0: 215 msg = AvailablePluginUpdatesMessage(available_updates, 216 new_recommended) 217 common.application.bus.send_message(msg) 218 219 updates_dfr = task.coiterate(iterate_plugins(plugins, installed, 220 available_updates, 221 new_recommended)) 222 updates_dfr.addCallback(send_message, 223 available_updates, new_recommended) 224 return updates_dfr 225 226 def failed_update(failure): 227 # Swallow all the errors. 228 return None 229 230 dfr = plugin_registry.update_cache() 231 dfr.addCallback(get_updates_and_new_recommended_list) 232 dfr.addErrback(failed_update) 233 return dfr 234
235 - def _reset_pending_call(self, result, callback):
236 pending = reactor.callLater(self.check_interval, 237 self._auto_request, callback) 238 self._pending_call = pending
239
240 - def _auto_request(self, callback):
241 self._current_dfr = dfr = self.request() 242 dfr.addCallback(self._got_response, callback) 243 dfr.addErrback(self._response_failed) 244 config = common.application.config 245 should_update_plugin_cache = \ 246 config.get_option('update_plugin_cache', section='plugin_registry') 247 if should_update_plugin_cache: 248 dfr.addCallback(self._update_plugin_cache) 249 dfr.addCallback(self._reset_pending_call, callback)
250
251 - def stop(self):
252 """ 253 Stop any pending loop or http calls 254 """ 255 try: 256 self._pending_call.cancel() 257 except (AttributeError, error.AlreadyCalled, error.AlreadyCancelled): 258 pass 259 260 try: 261 self._current_dfr.cancel() 262 except (AttributeError, defer.AlreadyCalledError): 263 pass 264 265 self._pending_call = None 266 self._current_dfr = None
267