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

« back to all changes in this revision

Viewing changes to ivle/webapp/media.py

Port ivle.webapp.admin.user's media to the new framework.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
'''Media file support for the framework.'''
21
21
 
22
22
import os
23
 
import time
24
23
import inspect
25
24
import mimetypes
26
 
import email.utils
27
25
 
 
26
import ivle.conf
28
27
from ivle.webapp.base.views import BaseView
29
 
from ivle.webapp.base.plugins import PublicViewPlugin, ViewPlugin, MediaPlugin
30
 
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
 
                      }
 
28
from ivle.webapp.base.plugins import BasePlugin
 
29
from ivle.webapp.errors import NotFound
40
30
 
41
31
def media_url(req, plugin, path):
42
 
    '''Generates a URL to a media file.
43
 
 
44
 
    Plugin can be a string, in which case it is put into the path literally,
45
 
    or a plugin object, in which case its name is looked up.
46
 
 
47
 
    If a version is specified in the IVLE configuration, a versioned URL will
48
 
    be generated.
 
32
    '''Generates a URL to a media file.'''
 
33
    return os.path.join(ivle.conf.root_dir, '+media',
 
34
                        req.reverse_plugins[plugin], path)
 
35
 
 
36
class MediaFileView(BaseView):
 
37
    '''A view for media files.
 
38
 
 
39
    This serves static files from directories registered by plugins.
 
40
 
 
41
    Plugins wishing to export media should declare a 'media' attribute,
 
42
    pointing to the directory to serve (relative to the module's directory).
 
43
    The contents of that directory will then be available under
 
44
    /+media/python.path.to.module.
49
45
    '''
50
 
    if not isinstance(plugin, basestring):
51
 
        plugin = req.config.reverse_plugins[plugin]
52
 
 
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
    def __init__(self, req, ns, path):
63
47
        self.ns = ns
64
48
        self.path = path
65
49
 
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
 
 
 
50
    def _make_filename(self, req):
 
51
        try:
 
52
            plugin = req.plugins[self.ns]
 
53
        except KeyError:
 
54
            raise NotFound()
 
55
 
 
56
        try:
94
57
            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
 
58
        except AttributeError:
 
59
            raise NotFound()
 
60
 
 
61
        plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
 
62
 
 
63
        return os.path.join(plugindir, mediadir, self.path)
102
64
 
103
65
    def render(self, req):
104
66
        # If it begins with ".." or separator, it's illegal. Die.
105
 
        if self.context.path.startswith("..") or \
106
 
           self.context.path.startswith('/'):
 
67
        if self.path.startswith("..") or self.path.startswith('/'):
107
68
            raise Forbidden()
108
69
 
109
 
        filename = self.get_filename(req)
110
 
        if filename is None:
111
 
            raise NotFound()
 
70
        filename = self._make_filename(req)
112
71
 
113
72
        # Find an appropriate MIME type.
114
73
        (type, _) = mimetypes.guess_type(filename)
117
76
 
118
77
        # Get out if it is unreadable or a directory.
119
78
        if not os.access(filename, os.F_OK):
120
 
            raise NotFound()
 
79
            raise NotFound(filename)
121
80
        if not os.access(filename, os.R_OK) or os.path.isdir(filename):
122
81
            raise Forbidden()
123
82
 
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
83
        req.content_type = type
132
84
        req.sendfile(filename)
133
85
 
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)
163
 
 
164
 
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
 
86
class Plugin(BasePlugin):
 
87
    urls = [
 
88
        ('+media/:ns/*path', MediaFileView),
 
89
    ]