48
43
if not isinstance(plugin, basestring):
49
44
plugin = req.config.reverse_plugins[plugin]
51
media_path = os.path.join('+media', '+' + req.config['media']['version']) \
52
if req.config['media']['version'] else '+media'
54
return req.make_path(os.path.join(media_path, plugin, path))
56
class MediaFile(object):
57
def __init__(self, root, external, version, ns, path):
59
self.external = external
60
self.version = version
48
media_path = os.path.join('+media', '+' + config['media']['version']) if \
49
config['media']['version'] else '+media'
51
return os.path.join(ivle.conf.root_dir, media_path, plugin, path)
53
class BaseMediaFileView(BaseView):
54
'''A view for media files.
56
This serves static files from directories registered by plugins.
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.
63
def __init__(self, req, ns, path):
68
extern = EXTERNAL_MEDIA_MAP[self.ns]
72
# Unless it's a whitelisted path, we don't want to hear about it.
73
if self.path not in extern[1]:
76
# Grab the admin-configured path for this particular external dep.
77
externdir = self.root.config['media']['externals'][extern[0]]
79
assert isinstance(externdir, basestring)
81
return os.path.join(externdir, self.path)
84
plugin = self.root.config.plugins[self.ns]
88
if not issubclass(plugin, MediaPlugin):
91
mediadir = plugin.media
92
plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
94
return os.path.join(plugindir, mediadir, self.path)
96
class MediaFileView(BaseView):
67
def _make_filename(self, req):
68
raise NotImplementedError()
99
70
def render(self, req):
100
71
# If it begins with ".." or separator, it's illegal. Die.
101
if self.context.path.startswith("..") or \
102
self.context.path.startswith('/'):
72
if self.path.startswith("..") or self.path.startswith('/'):
105
filename = self.get_filename(req)
75
filename = self._make_filename(req)
109
77
# Find an appropriate MIME type.
110
78
(type, _) = mimetypes.guess_type(filename)
117
85
if not os.access(filename, os.R_OK) or os.path.isdir(filename):
120
if self.context.version is not None:
121
req.headers_out['Expires'] = email.utils.formatdate(
122
timeval=time.time() + (60*60*24*365),
127
88
req.content_type = type
128
89
req.sendfile(filename)
130
def get_filename(self, req):
131
return self.context.filename
133
def get_permissions(self, user, config):
136
def root_to_media(root, *segments):
137
if segments[0].startswith('+'):
138
if segments[0] == '+external':
143
version = segments[0][1:]
144
if segments[1] == '+external':
155
if version is not None and version != root.config['media']['version']:
159
path = os.path.normpath(os.path.join(*path[1:]))
161
return MediaFile(root, external, version, ns, path)
92
class MediaFileView(BaseMediaFileView):
93
'''A view for media files.
95
This serves static files from directories registered by plugins.
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.
104
def _make_filename(self, req):
106
plugin = req.config.plugins[self.ns]
110
if not issubclass(plugin, MediaPlugin):
113
mediadir = plugin.media
114
plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
116
return os.path.join(plugindir, mediadir, self.path)
118
def get_permissions(self, user):
121
class VersionedMediaFileView(MediaFileView):
122
'''A view for versioned media files, with aggressive caching.
124
This serves static media files with a version string, and requests that
125
browsers cache them for a long time.
128
def __init__(self, req, ns, path, version):
129
super(VersionedMediaFileView, self).__init__(req, ns, path)
130
self.version = version
132
def _make_filename(self, req):
133
if self.version != Config()['media']['version']:
136
# Don't expire for a year.
137
req.headers_out['Expires'] = email.utils.formatdate(
138
timeval=time.time() + (60*60*24*365),
141
return super(VersionedMediaFileView, self)._make_filename(req)
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'])}
149
class ExternalMediaFileView(BaseMediaFileView):
150
'''A view for media files from external dependencies.
152
This serves specific static files from external dependencies as defined in
153
the IVLE configuration.
157
def _make_filename(self, req):
159
extern = EXTERNAL_MEDIA_MAP[self.ns]
163
# Unless it's a whitelisted path, we don't want to hear about it.
164
if self.path not in extern[1]:
167
# Grab the admin-configured path for this particular external dep.
169
externdir = config['media']['externals'][extern[0]]
171
assert isinstance(externdir, basestring)
173
return os.path.join(externdir, self.path)
175
def get_permissions(self, user):
178
class ExternalVersionedMediaFileView(ExternalMediaFileView):
179
'''A view for versioned media files from external dependencies, with
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.
186
def __init__(self, req, ns, path, version):
187
super(ExternalVersionedMediaFileView, self).__init__(req, ns, path)
188
self.version = version
190
def _make_filename(self, req):
191
if self.version != Config()['media']['version']:
194
# Don't expire for a year.
195
req.headers_out['Expires'] = email.utils.formatdate(
196
timeval=time.time() + (60*60*24*365),
199
return super(ExternalVersionedMediaFileView, self)._make_filename(req)
163
202
class Plugin(ViewPlugin, PublicViewPlugin):
164
forward_routes = [(ApplicationRoot, '+media', root_to_media, INF)]
165
views = [(MediaFile, '+index', MediaFileView)]
166
public_forward_routes = forward_routes
204
('+media/+:version/+external/:ns/*path', ExternalVersionedMediaFileView),
205
('+media/+external/:ns/*path', ExternalMediaFileView),
206
('+media/+:version/:ns/*path', VersionedMediaFileView),
207
('+media/:ns/*path', MediaFileView),