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

« back to all changes in this revision

Viewing changes to ivle/webapp/media.py

Replaced Python config files (conf.py) with new config files system, using
    configobj (INI-file style config files).

setup.py config now produces ./etc/ivle.conf, a new-style config file.
ivle/conf/conf.py is now part of the IVLE source code. It reads the new config
file and provides the same legacy interface, so all of IVLE still functions,
including setup.py config.

Added /etc to the source tree (config files will be stored here).
Added configobj as a dependency in doc/setup/install_proc.txt.

setup.py install copies ./etc/ivle.conf into /etc/ivle/ivle.conf.

Removed boilerplate code generation from setup/configure.py (that code is now
part of ivle/conf/conf.py which is now in the source tree).

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
 
28
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
30
29
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
30
 
41
31
def media_url(req, plugin, path):
42
32
    '''Generates a URL to a media file.
43
 
 
 
33
    
44
34
    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.
 
35
    or a plugin object, in which case its name is looked up.'''
 
36
    if not isinstance(plugin, basestring):
 
37
        plugin = req.reverse_plugins[plugin]
 
38
 
 
39
    return os.path.join(ivle.conf.root_dir, '+media', plugin, path)
 
40
 
 
41
class MediaFileView(BaseView):
 
42
    '''A view for media files.
 
43
 
 
44
    This serves static files from directories registered by plugins.
 
45
 
 
46
    Plugins wishing to export media should declare a 'media' attribute,
 
47
    pointing to the directory to serve (relative to the module's directory).
 
48
    The contents of that directory will then be available under
 
49
    /+media/python.path.to.module.
49
50
    '''
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
 
51
    def __init__(self, req, ns, path):
63
52
        self.ns = ns
64
53
        self.path = path
65
54
 
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
 
55
    def _make_filename(self, req):
 
56
        try:
 
57
            plugin = req.plugins[self.ns]
 
58
        except KeyError:
 
59
            raise NotFound()
 
60
 
 
61
        if not issubclass(plugin, MediaPlugin):
 
62
            raise NotFound()
 
63
 
 
64
        mediadir = plugin.media
 
65
        plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
 
66
 
 
67
        return os.path.join(plugindir, mediadir, self.path)
102
68
 
103
69
    def render(self, req):
104
70
        # If it begins with ".." or separator, it's illegal. Die.
105
 
        if self.context.path.startswith("..") or \
106
 
           self.context.path.startswith('/'):
 
71
        if self.path.startswith("..") or self.path.startswith('/'):
107
72
            raise Forbidden()
108
73
 
109
 
        filename = self.get_filename(req)
110
 
        if filename is None:
111
 
            raise NotFound()
 
74
        filename = self._make_filename(req)
112
75
 
113
76
        # Find an appropriate MIME type.
114
77
        (type, _) = mimetypes.guess_type(filename)
121
84
        if not os.access(filename, os.R_OK) or os.path.isdir(filename):
122
85
            raise Forbidden()
123
86
 
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
87
        req.content_type = type
132
88
        req.sendfile(filename)
133
89
 
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
 
90
class Plugin(ViewPlugin):
 
91
    urls = [
 
92
        ('+media/:ns/*path', MediaFileView),
 
93
    ]