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