~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/media.py

  • Committer: William Grant
  • Date: 2009-04-28 04:50:39 UTC
  • Revision ID: grantw@unimelb.edu.au-20090428045039-ibb7gwtjrhe9osq3
Populate req.config in a cleaner manner.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import mimetypes
26
26
import email.utils
27
27
 
 
28
import ivle.conf
 
29
from ivle.config import Config
28
30
from ivle.webapp.base.views import BaseView
29
31
from ivle.webapp.base.plugins import PublicViewPlugin, ViewPlugin, MediaPlugin
30
32
from ivle.webapp.errors import NotFound, Forbidden
31
 
from ivle.webapp.publisher import INF
32
 
from ivle.webapp import ApplicationRoot
33
 
 
34
 
# This maps a media namespace to an external dependency directory (in this
35
 
# case specified by the configuration option media/externals/jquery) and a
36
 
# list of permitted subpaths.
37
 
EXTERNAL_MEDIA_MAP = {'jquery': ('jquery', ['jquery.js']),
38
 
                      'codemirror': ('codemirror', None),
39
 
                      }
40
33
 
41
34
def media_url(req, plugin, path):
42
35
    '''Generates a URL to a media file.
50
43
    if not isinstance(plugin, basestring):
51
44
        plugin = req.config.reverse_plugins[plugin]
52
45
 
53
 
    media_path = os.path.join('+media', '+' + req.config['media']['version']) \
54
 
                    if req.config['media']['version'] else '+media'
55
 
 
56
 
    return req.make_path(os.path.join(media_path, plugin, path))
57
 
 
58
 
class MediaFile(object):
59
 
    def __init__(self, root, external, version, ns, path):
60
 
        self.root = root
61
 
        self.external = external
62
 
        self.version = version
 
46
    config = Config()
 
47
 
 
48
    media_path = os.path.join('+media', '+' + config['media']['version']) if \
 
49
                              config['media']['version'] else '+media'
 
50
 
 
51
    return os.path.join(ivle.conf.root_dir, media_path, plugin, path)
 
52
 
 
53
class BaseMediaFileView(BaseView):
 
54
    '''A view for media files.
 
55
 
 
56
    This serves static files from directories registered by plugins.
 
57
 
 
58
    Plugins wishing to export media should declare a 'media' attribute,
 
59
    pointing to the directory to serve (relative to the module's directory).
 
60
    The contents of that directory will then be available under
 
61
    /+media/python.path.to.module.
 
62
    '''
 
63
    def __init__(self, req, ns, path):
63
64
        self.ns = ns
64
65
        self.path = path
65
66
 
66
 
    @property
67
 
    def filename(self):
68
 
        if self.external:
69
 
            try:
70
 
                extern = EXTERNAL_MEDIA_MAP[self.ns]
71
 
            except KeyError:
72
 
                return None
73
 
 
74
 
            # Unless it's a whitelisted path, we don't want to hear about it.
75
 
            # If the whitelist is None (not []), we allow all.
76
 
            if extern[1] is not None and self.path not in extern[1]:
77
 
                return None
78
 
 
79
 
            # Grab the admin-configured path for this particular external dep.
80
 
            externdir = self.root.config['media']['externals'][extern[0]]
81
 
 
82
 
            assert isinstance(externdir, basestring)
83
 
 
84
 
            return os.path.join(externdir, self.path)
85
 
        else:
86
 
            try:
87
 
                plugin = self.root.config.plugins[self.ns]
88
 
            except KeyError:
89
 
                return None
90
 
 
91
 
            if not issubclass(plugin, MediaPlugin):
92
 
                return None
93
 
 
94
 
            mediadir = plugin.media
95
 
            plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
96
 
 
97
 
            return os.path.join(plugindir, mediadir, self.path)
98
 
 
99
 
class MediaFileView(BaseView):
100
 
    def authorize(self, req):
101
 
        return True
 
67
    def _make_filename(self, req):
 
68
        raise NotImplementedError()
102
69
 
103
70
    def render(self, req):
104
71
        # If it begins with ".." or separator, it's illegal. Die.
105
 
        if self.context.path.startswith("..") or \
106
 
           self.context.path.startswith('/'):
 
72
        if self.path.startswith("..") or self.path.startswith('/'):
107
73
            raise Forbidden()
108
74
 
109
 
        filename = self.get_filename(req)
110
 
        if filename is None:
111
 
            raise NotFound()
 
75
        filename = self._make_filename(req)
112
76
 
113
77
        # Find an appropriate MIME type.
114
78
        (type, _) = mimetypes.guess_type(filename)
121
85
        if not os.access(filename, os.R_OK) or os.path.isdir(filename):
122
86
            raise Forbidden()
123
87
 
124
 
        if self.context.version is not None:
125
 
            req.headers_out['Expires'] = email.utils.formatdate(
126
 
                                timeval=time.time() + (60*60*24*365),
127
 
                                localtime=False,
128
 
                                usegmt=True)
129
 
 
130
 
 
131
88
        req.content_type = type
132
89
        req.sendfile(filename)
133
90
 
134
 
    def get_filename(self, req):
135
 
        return self.context.filename
136
 
 
137
 
def root_to_media(root, *segments):
138
 
    if segments[0].startswith('+'):
139
 
        if segments[0] == '+external':
140
 
            external = True
141
 
            version = None
142
 
            path = segments[1:]
143
 
        else:
144
 
            version = segments[0][1:]
145
 
            if segments[1] == '+external':
146
 
                external = True
147
 
                path = segments[2:]
148
 
            else:
149
 
                external = False
150
 
                path = segments[1:]
151
 
    else:
152
 
        external = False
153
 
        version = None
154
 
        path = segments
155
 
 
156
 
    if version is not None and version != root.config['media']['version']:
157
 
        return None
158
 
 
159
 
    ns = path[0]
160
 
    path = os.path.normpath(os.path.join(*path[1:]))
161
 
 
162
 
    return MediaFile(root, external, version, ns, path)
 
91
 
 
92
class MediaFileView(BaseMediaFileView):
 
93
    '''A view for media files.
 
94
 
 
95
    This serves static files from directories registered by plugins.
 
96
 
 
97
    Plugins wishing to export media should declare a 'media' attribute,
 
98
    pointing to the directory to serve (relative to the module's directory).
 
99
    The contents of that directory will then be available under
 
100
    /+media/python.path.to.module.
 
101
    '''
 
102
    permission = None
 
103
 
 
104
    def _make_filename(self, req):
 
105
        try:
 
106
            plugin = req.config.plugins[self.ns]
 
107
        except KeyError:
 
108
            raise NotFound()
 
109
 
 
110
        if not issubclass(plugin, MediaPlugin):
 
111
            raise NotFound()
 
112
 
 
113
        mediadir = plugin.media
 
114
        plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
 
115
 
 
116
        return os.path.join(plugindir, mediadir, self.path)
 
117
 
 
118
    def get_permissions(self, user):
 
119
        return set()
 
120
 
 
121
class VersionedMediaFileView(MediaFileView):
 
122
    '''A view for versioned media files, with aggressive caching.
 
123
 
 
124
    This serves static media files with a version string, and requests that
 
125
    browsers cache them for a long time.
 
126
    '''
 
127
 
 
128
    def __init__(self, req, ns, path, version):
 
129
        super(VersionedMediaFileView, self).__init__(req, ns, path)
 
130
        self.version = version
 
131
 
 
132
    def _make_filename(self, req):
 
133
        if self.version != Config()['media']['version']:
 
134
            raise NotFound()
 
135
 
 
136
        # Don't expire for a year.
 
137
        req.headers_out['Expires'] = email.utils.formatdate(
 
138
                                    timeval=time.time() + (60*60*24*365),
 
139
                                    localtime=False,
 
140
                                    usegmt=True)
 
141
        return super(VersionedMediaFileView, self)._make_filename(req)
 
142
 
 
143
 
 
144
# This maps a media namespace to an external dependency directory (in this
 
145
# case specified by the configuration option media/externals/jquery) and a
 
146
# list of permitted subpaths.
 
147
EXTERNAL_MEDIA_MAP = {'jquery': ('jquery', ['jquery.js'])}
 
148
 
 
149
class ExternalMediaFileView(BaseMediaFileView):
 
150
    '''A view for media files from external dependencies.
 
151
 
 
152
    This serves specific static files from external dependencies as defined in
 
153
    the IVLE configuration.
 
154
    '''
 
155
    permission = None
 
156
 
 
157
    def _make_filename(self, req):
 
158
        try:
 
159
            extern = EXTERNAL_MEDIA_MAP[self.ns]
 
160
        except KeyError:
 
161
            raise NotFound()
 
162
 
 
163
        # Unless it's a whitelisted path, we don't want to hear about it.
 
164
        if self.path not in extern[1]:
 
165
            raise NotFound()
 
166
 
 
167
        # Grab the admin-configured path for this particular external dep.
 
168
        config = Config()
 
169
        externdir = config['media']['externals'][extern[0]]
 
170
 
 
171
        assert isinstance(externdir, basestring)
 
172
 
 
173
        return os.path.join(externdir, self.path)
 
174
 
 
175
    def get_permissions(self, user):
 
176
        return set()
 
177
 
 
178
class ExternalVersionedMediaFileView(ExternalMediaFileView):
 
179
    '''A view for versioned media files from external dependencies, with
 
180
    aggressive caching.
 
181
 
 
182
    This serves specific static media files from external dependencies with a
 
183
    version string, and requests that browsers cache them for a long time.
 
184
    '''
 
185
 
 
186
    def __init__(self, req, ns, path, version):
 
187
        super(ExternalVersionedMediaFileView, self).__init__(req, ns, path)
 
188
        self.version = version
 
189
 
 
190
    def _make_filename(self, req):
 
191
        if self.version != Config()['media']['version']:
 
192
            raise NotFound()
 
193
 
 
194
        # Don't expire for a year.
 
195
        req.headers_out['Expires'] = email.utils.formatdate(
 
196
                                    timeval=time.time() + (60*60*24*365),
 
197
                                    localtime=False,
 
198
                                    usegmt=True)
 
199
        return super(ExternalVersionedMediaFileView, self)._make_filename(req)
 
200
 
163
201
 
164
202
class Plugin(ViewPlugin, PublicViewPlugin):
165
 
    forward_routes = [(ApplicationRoot, '+media', root_to_media, INF)]
166
 
    views = [(MediaFile, '+index', MediaFileView)]
167
 
    public_forward_routes = forward_routes
168
 
    public_views = views
 
203
    urls = [
 
204
        ('+media/+:version/+external/:ns/*path', ExternalVersionedMediaFileView),
 
205
        ('+media/+external/:ns/*path', ExternalMediaFileView),
 
206
        ('+media/+:version/:ns/*path', VersionedMediaFileView),
 
207
        ('+media/:ns/*path', MediaFileView),
 
208
    ]
 
209
 
 
210
    public_urls = urls