2 CMS dynamic application clients module
4 Helper module to automatically retrieve client download links from different
5 sources (e.g. redmine files page).
8 import urllib, urllib2, cookielib, urlparse
10 from datetime import datetime
13 from synnefo import settings
15 CLIENTS_CACHE_TIMEOUT = getattr(settings, 'CLOUDCMS_CLIENTS_CACHE_TIMEOUT', 120)
17 class VersionSource(object):
19 Base class for the different version source handlers.
21 def __init__(self, link=None, os="linux", arch="all", regex=".", name=None,
22 cache_backend=None, extra_options={}):
27 extra_options.update({'source_type': self.source_type, 'os': os})
28 self.extra_version_options = extra_options
30 self.cache_backend = cache_backend
31 self.cache_key = self.os + self.arch + self.link
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()))
43 def get_url(self, url):
45 Load url content and return the html etree object.
47 return html.document_fromstring(self.opener.open(url).read())
51 Fill self.versions attribute with dict objects of the following format
53 {'date': datetime.datetime(2012, 3, 16, 14, 29),
54 'link': 'http://www.domain.com/clientdownload.exe',
55 'name': 'Client download',
63 Load wrapper which handles versions caching if cache_backend is set
65 if self.cache_backend:
66 self.versions = self.cache_backend.get(self.cache_key)
71 if self.cache_backend:
72 self.cache_backend.set(self.cache_key, self.versions, CLIENTS_CACHE_TIMEOUT)
76 Return the latest versions
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)
91 class RedmineSource(VersionSource):
93 Parse a redmine project files page and return the list of existing files.
95 source_type = 'redmine_files'
99 Load redmine files url and extract downloads. Also parse date to be
100 able to identify latest download.
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')]")
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}
115 versions = map(_parse_row, files)
116 versions.sort(reverse=True, key=lambda r:r['date'])
117 self.versions = versions
121 class DirectSource(VersionSource):
123 Direct link to a version. Dummy VersionSource which always returns one entry
124 for the provided link.
126 source_type = 'direct'
129 self.versions = [{'name': self.name, 'link': self.link, 'date': None}]
133 class LinkSource(DirectSource):
135 Used when version exists in some other url (e.g. apple store client)
140 class ClientVersions(object):
142 Client versions manager. Given a sources dict like::
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'],
150 initializes a dict of proper VersionSource objects.
153 def __init__(self, sources, cache_backend=None):
154 self._sources = sources
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)
164 def get_latest_versions(self):
166 Return the latest version of each version source.
168 for os, source in self.sources.iteritems():
169 yield source.get_latest()
172 # SOURCE TYPES CLASS MAP
174 'redmine_files': RedmineSource,
175 'direct': DirectSource,