1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
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.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""Checks the AUTOUPDATE_URL to see if there's a more recent version
32
Call ``check_for_updates``.
37
from miro import prefs
38
from miro import config
39
from miro import eventloop
40
from miro import feedparser
41
from miro import signals
43
from miro.httpclient import grabURL
45
check_in_progress = False
47
def check_for_updates(up_to_date_callback=None):
48
"""Checks the AUTOUPDATE_URL for the recent version.
50
The ``up_to_date_callback`` is a function that should take no
51
arguments and return nothing.
54
if miro.plat.AUTOUPDATE == False:
55
logging.info("this platform has autoupdate disabled. skipping.")
58
global check_in_progress
59
if not check_in_progress:
60
check_in_progress = True
61
logging.info("Checking for updates...")
62
url = config.get(prefs.AUTOUPDATE_URL)
63
update_handler = lambda data: _handle_app_cast(data,
65
error_handler = _handle_error
66
grabURL(url, update_handler, error_handler)
68
def _handle_error(error):
70
global check_in_progress
71
check_in_progress = False
72
logging.warn("HTTP error while checking for updates %s", error)
73
eventloop.addTimeout(86400, check_for_updates, "Check for updates")
75
def _handle_app_cast(data, up_to_date_callback):
76
"""Handle appcast data when it's correctly fetched
78
# python 2.4 requires that except and finally clauses be in different
82
appcast = feedparser.parse(data['body'])
83
if appcast['bozo'] == '1':
87
latest = _get_item_for_latest(appcast)
89
logging.info('No updates for this platform.')
90
# this will go through the finally clause below
93
serial = int(config.get(prefs.APP_SERIAL))
94
up_to_date = (serial >= _get_item_serial(latest))
97
logging.info('New update available.')
98
signals.system.update_available(latest)
99
elif up_to_date_callback:
100
logging.info('Up to date. Notifying callback.')
101
up_to_date_callback()
103
logging.info('Up to date.')
105
except (SystemExit, KeyboardInterrupt):
108
logging.exception("Error while handling appcast data.")
111
global check_in_progress
112
check_in_progress = False
113
eventloop.addTimeout(86400, check_for_updates, "Check for updates")
115
def _get_item_for_latest(appcast):
116
"""Filter out non platform items, sort remaining from latest to
117
oldest and return the item corresponding to the latest known
120
If there are no entries for this platform (this happens with
121
Linux), then this returns None.
123
platform = config.get(prefs.APP_PLATFORM)
126
for item in appcast['entries']:
127
rejected_enclosures = []
128
for enclosure in item['enclosures']:
129
if enclosure['dtv:platform'] != platform:
130
rejected_enclosures.append(enclosure)
131
if enclosure['type'] != 'application/octet-stream':
132
rejected_enclosures.append(enclosure)
133
for enclosure in rejected_enclosures:
134
item['enclosures'].remove(enclosure)
135
if len(item['enclosures']) == 0:
136
rejected_items.append(item)
138
for item in rejected_items:
139
appcast['entries'].remove(item)
141
# we've removed all entries that aren't relevant to this platform.
142
# if there aren't any left, we return None and the caller can deal
144
if not appcast['entries']:
147
appcast['entries'].sort(key=_get_item_serial, reverse=True)
148
return appcast['entries'][0]
150
def _get_item_serial(item):
151
"""Returns the serial of the first enclosure of the passed item
153
return int(item['enclosures'][0]['dtv:serial'])