Statistics
| Branch: | Tag: | Revision:

root / cloudcms / clients.py @ d240ebcb

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
}