Statistics
| Branch: | Tag: | Revision:

root / cloudcms / clients.py @ 10118ccf

History | View | Annotate | Download (5.3 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
        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})
30

    
31
        self.cache_backend = cache_backend
32
        self.cache_key = self.os + self.arch + self.link
33

    
34
        if not name:
35
            self.name = os
36

    
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()))
43

    
44
    def get_url(self, url):
45
        """
46
        Load url content and return the html etree object.
47
        """
48
        return html.document_fromstring(self.opener.open(url).read())
49

    
50
    def load(self):
51
        """
52
        Fill self.versions attribute with dict objects of the following format
53

54
        {'date': datetime.datetime(2012, 3, 16, 14, 29),
55
         'link': 'http://www.domain.com/clientdownload.exe',
56
         'name': 'Client download',
57
         'os': 'windows',
58
         'version': None}
59
        """
60
        raise NotImplemented
61

    
62
    def update(self):
63
        """
64
        Load wrapper which handles versions caching if cache_backend is set
65
        """
66
        if self.cache_backend:
67
            self.versions = self.cache_backend.get(self.cache_key)
68

    
69
        if not self.versions:
70
            self.load()
71

    
72
        if self.cache_backend:
73
            self.cache_backend.set(self.cache_key, self.versions, CLIENTS_CACHE_TIMEOUT)
74

    
75
    def get_latest(self):
76
        """
77
        Return the latest versions
78
        """
79

    
80
        # update versions
81
        self.update()
82

    
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)
87
            return version
88

    
89
        return None
90

    
91

    
92
class RedmineSource(VersionSource):
93
    """
94
    Parse a redmine project files page and return the list of existing files.
95
    """
96
    source_type = 'redmine_files'
97

    
98
    def load(self):
99
        """
100
        Load redmine files url and extract downloads. Also parse date to be
101
        able to identify latest download.
102
        """
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')]")
107

    
108
        # helper lambdas
109
        def _parse_row(row):
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}
115

    
116
        versions = map(_parse_row, files)
117
        versions.sort(reverse=True, key=lambda r:r['date'])
118
        self.versions = versions
119
        return self
120

    
121

    
122
class DirectSource(VersionSource):
123
    """
124
    Direct link to a version. Dummy VersionSource which always returns one entry
125
    for the provided link.
126
    """
127
    source_type = 'direct'
128

    
129
    def load(self):
130
        self.versions = [{'name': self.name, 'link': self.link, 'date': None}]
131
        return self.versions
132

    
133

    
134
class LinkSource(DirectSource):
135
    """
136
    Used when version exists in some other url (e.g. apple store client)
137
    """
138
    source_type = 'link'
139

    
140

    
141
class ClientVersions(object):
142
    """
143
    Client versions manager. Given a sources dict like::
144

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'],
149
     'kwargs': {}}}
150

151
    initializes a dict of proper VersionSource objects.
152
    """
153

    
154
    def __init__(self, sources, cache_backend=None):
155
        self._sources = sources
156
        self.sources = {}
157

    
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)
164

    
165
    def get_latest_versions(self):
166
        """
167
        Return the latest version of each version source.
168
        """
169
        for os, source in self.sources.iteritems():
170
            yield source.get_latest()
171

    
172

    
173
# SOURCE TYPES CLASS MAP
174
SOURCE_TYPES = {
175
    'redmine_files': RedmineSource,
176
    'direct': DirectSource,
177
    'link': LinkSource
178
}
179