root / cloudcms / clients.py @ 511913cb
History | View | Annotate | Download (5.2 kB)
1 |
"""
|
---|---|
2 |
CMS dynamic application clients module
|
3 |
|
4 |
Helper module to automatically retrieve client download links from different
|
5 |
sources (e.g. redmine files page).
|
6 |
"""
|
7 |
|
8 |
import urllib, urllib2, cookielib, urlparse |
9 |
|
10 |
from datetime import datetime |
11 |
from lxml import html |
12 |
|
13 |
from synnefo import settings |
14 |
|
15 |
CLIENTS_CACHE_TIMEOUT = getattr(settings, 'CLOUDCMS_CLIENTS_CACHE_TIMEOUT', 120) |
16 |
|
17 |
class VersionSource(object): |
18 |
"""
|
19 |
Base class for the different version source handlers.
|
20 |
"""
|
21 |
def __init__(self, link=None, os="linux", arch="all", regex=".", name=None, |
22 |
cache_backend=None, extra_options={}):
|
23 |
self.os = os
|
24 |
self.arch = arch
|
25 |
self.link = link
|
26 |
self.versions = []
|
27 |
extra_options.update({'source_type': self.source_type, 'os': os}) |
28 |
self.extra_version_options = extra_options
|
29 |
|
30 |
self.cache_backend = cache_backend
|
31 |
self.cache_key = self.os + self.arch + self.link |
32 |
|
33 |
if not name: |
34 |
self.name = os
|
35 |
|
36 |
# generic urllib2 opener
|
37 |
self.opener = urllib2.build_opener(
|
38 |
urllib2.HTTPRedirectHandler(), |
39 |
urllib2.HTTPHandler(debuglevel=0),
|
40 |
urllib2.HTTPSHandler(debuglevel=0),
|
41 |
urllib2.HTTPCookieProcessor(cookielib.CookieJar())) |
42 |
|
43 |
def get_url(self, url): |
44 |
"""
|
45 |
Load url content and return the html etree object.
|
46 |
"""
|
47 |
return html.document_fromstring(self.opener.open(url).read()) |
48 |
|
49 |
def load(self): |
50 |
"""
|
51 |
Fill self.versions attribute with dict objects of the following format
|
52 |
|
53 |
{'date': datetime.datetime(2012, 3, 16, 14, 29),
|
54 |
'link': 'http://www.domain.com/clientdownload.exe',
|
55 |
'name': 'Client download',
|
56 |
'os': 'windows',
|
57 |
'version': None}
|
58 |
"""
|
59 |
raise NotImplemented |
60 |
|
61 |
def update(self): |
62 |
"""
|
63 |
Load wrapper which handles versions caching if cache_backend is set
|
64 |
"""
|
65 |
if self.cache_backend: |
66 |
self.versions = self.cache_backend.get(self.cache_key) |
67 |
|
68 |
if not self.versions: |
69 |
self.load()
|
70 |
|
71 |
if self.cache_backend: |
72 |
self.cache_backend.set(self.cache_key, self.versions, CLIENTS_CACHE_TIMEOUT) |
73 |
|
74 |
def get_latest(self): |
75 |
"""
|
76 |
Return the latest versions
|
77 |
"""
|
78 |
|
79 |
# update versions
|
80 |
self.update()
|
81 |
|
82 |
# check that at least one version is available
|
83 |
if len(self.versions): |
84 |
version = self.versions[0] |
85 |
version.update(self.extra_version_options)
|
86 |
return version
|
87 |
|
88 |
return None |
89 |
|
90 |
|
91 |
class RedmineSource(VersionSource): |
92 |
"""
|
93 |
Parse a redmine project files page and return the list of existing files.
|
94 |
"""
|
95 |
source_type = 'redmine_files'
|
96 |
|
97 |
def load(self): |
98 |
"""
|
99 |
Load redmine files url and extract downloads. Also parse date to be
|
100 |
able to identify latest download.
|
101 |
"""
|
102 |
spliturl = urlparse.urlsplit(self.link)
|
103 |
baseurl = spliturl.geturl().replace(spliturl.path, '')
|
104 |
html = self.get_url(self.link) |
105 |
files = html.xpath("//tr[contains(@class, 'file')]")
|
106 |
|
107 |
# helper lambdas
|
108 |
def _parse_row(row): |
109 |
name = row.xpath("td[@class='filename']/a")[0].text |
110 |
link = baseurl + row.xpath("td[@class='filename']/a")[0].attrib.get('href') |
111 |
strdate = row.xpath("td[@class='created_on']")[0].text |
112 |
date = datetime.strptime(strdate, '%m/%d/%Y %I:%M %p')
|
113 |
return {'name': name, 'link': link, 'date': date, 'version': None} |
114 |
|
115 |
versions = map(_parse_row, files)
|
116 |
versions.sort(reverse=True, key=lambda r:r['date']) |
117 |
self.versions = versions
|
118 |
return self |
119 |
|
120 |
|
121 |
class DirectSource(VersionSource): |
122 |
"""
|
123 |
Direct link to a version. Dummy VersionSource which always returns one entry
|
124 |
for the provided link.
|
125 |
"""
|
126 |
source_type = 'direct'
|
127 |
|
128 |
def load(self): |
129 |
self.versions = [{'name': self.name, 'link': self.link, 'date': None}] |
130 |
return self.versions |
131 |
|
132 |
|
133 |
class LinkSource(DirectSource): |
134 |
"""
|
135 |
Used when version exists in some other url (e.g. apple store client)
|
136 |
"""
|
137 |
source_type = 'link'
|
138 |
|
139 |
|
140 |
class ClientVersions(object): |
141 |
"""
|
142 |
Client versions manager. Given a sources dict like::
|
143 |
|
144 |
{'windows': {'source_type': 'direct', 'args':
|
145 |
['http://clients.com/win.exe'], 'kwargs': {}},
|
146 |
'linux': {'redmine_files': 'direct',
|
147 |
'args': ['http://redmine.com/projects/client/files'],
|
148 |
'kwargs': {}}}
|
149 |
|
150 |
initializes a dict of proper VersionSource objects.
|
151 |
"""
|
152 |
|
153 |
def __init__(self, sources, cache_backend=None): |
154 |
self._sources = sources
|
155 |
self.sources = {}
|
156 |
|
157 |
for s in self._sources: |
158 |
source_params = self._sources.get(s)
|
159 |
if source_params['type'] in SOURCE_TYPES: |
160 |
kwargs = {'os': s, 'cache_backend': cache_backend} |
161 |
args = source_params.get('args', [])
|
162 |
self.sources[s] = SOURCE_TYPES[source_params['type']](*args, **kwargs) |
163 |
|
164 |
def get_latest_versions(self): |
165 |
"""
|
166 |
Return the latest version of each version source.
|
167 |
"""
|
168 |
for os, source in self.sources.iteritems(): |
169 |
yield source.get_latest()
|
170 |
|
171 |
|
172 |
# SOURCE TYPES CLASS MAP
|
173 |
SOURCE_TYPES = { |
174 |
'redmine_files': RedmineSource,
|
175 |
'direct': DirectSource,
|
176 |
'link': LinkSource
|
177 |
} |
178 |
|