Statistics
| Branch: | Tag: | Revision:

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