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 self.extra_version_options = {}
28 self.extra_version_options.update(extra_options)
29 self.extra_version_options.update({'source_type': self.source_type, 'os': self.os})
31 self.cache_backend = cache_backend
32 self.cache_key = self.os + self.arch + self.link
37 # generic urllib2 opener
38 self.opener = urllib2.build_opener(
39 urllib2.HTTPRedirectHandler(),
40 urllib2.HTTPHandler(debuglevel=0),
41 urllib2.HTTPSHandler(debuglevel=0),
42 urllib2.HTTPCookieProcessor(cookielib.CookieJar()))
44 def get_url(self, url):
46 Load url content and return the html etree object.
48 return html.document_fromstring(self.opener.open(url).read())
52 Fill self.versions attribute with dict objects of the following format
54 {'date': datetime.datetime(2012, 3, 16, 14, 29),
55 'link': 'http://www.domain.com/clientdownload.exe',
56 'name': 'Client download',
64 Load wrapper which handles versions caching if cache_backend is set
66 if self.cache_backend:
67 self.versions = self.cache_backend.get(self.cache_key)
72 if self.cache_backend:
73 self.cache_backend.set(self.cache_key, self.versions, CLIENTS_CACHE_TIMEOUT)
77 Return the latest versions
83 # check that at least one version is available
84 if len(self.versions):
85 version = self.versions[0]
86 version.update(self.extra_version_options)
92 class RedmineSource(VersionSource):
94 Parse a redmine project files page and return the list of existing files.
96 source_type = 'redmine_files'
100 Load redmine files url and extract downloads. Also parse date to be
101 able to identify latest download.
103 spliturl = urlparse.urlsplit(self.link)
104 baseurl = spliturl.geturl().replace(spliturl.path, '')
105 html = self.get_url(self.link)
106 files = html.xpath("//tr[contains(@class, 'file')]")
110 name = row.xpath("td[@class='filename']/a")[0].text
111 link = baseurl + row.xpath("td[@class='filename']/a")[0].attrib.get('href')
112 strdate = row.xpath("td[@class='created_on']")[0].text
113 date = datetime.strptime(strdate, '%m/%d/%Y %I:%M %p')
114 return {'name': name, 'link': link, 'date': date, 'version': None}
116 versions = map(_parse_row, files)
117 versions.sort(reverse=True, key=lambda r:r['date'])
118 self.versions = versions
122 class DirectSource(VersionSource):
124 Direct link to a version. Dummy VersionSource which always returns one entry
125 for the provided link.
127 source_type = 'direct'
130 self.versions = [{'name': self.name, 'link': self.link, 'date': None}]
134 class LinkSource(DirectSource):
136 Used when version exists in some other url (e.g. apple store client)
141 class ClientVersions(object):
143 Client versions manager. Given a sources dict like::
145 {'windows': {'source_type': 'direct', 'args':
146 ['http://clients.com/win.exe'], 'kwargs': {}},
147 'linux': {'redmine_files': 'direct',
148 'args': ['http://redmine.com/projects/client/files'],
151 initializes a dict of proper VersionSource objects.
154 def __init__(self, sources, cache_backend=None):
155 self._sources = sources
158 for s in self._sources:
159 source_params = self._sources.get(s)
160 if source_params['type'] in SOURCE_TYPES:
161 kwargs = {'os': s, 'cache_backend': cache_backend}
162 args = source_params.get('args', [])
163 self.sources[s] = SOURCE_TYPES[source_params['type']](*args, **kwargs)
165 def get_latest_versions(self):
167 Return the latest version of each version source.
169 for os, source in self.sources.iteritems():
170 yield source.get_latest()
173 # SOURCE TYPES CLASS MAP
175 'redmine_files': RedmineSource,
176 'direct': DirectSource,