It's 2012 all up in here
[mediagoblin.git] / mediagoblin / app.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 import urllib
19
20 import routes
21 from webob import Request, exc
22
23 from mediagoblin import routing, meddleware
24 from mediagoblin.tools import common, translate, template
25 from mediagoblin.tools.response import render_404
26 from mediagoblin.tools import request as mg_request
27 from mediagoblin.mg_globals import setup_globals
28 from mediagoblin.init.celery import setup_celery_from_config
29 from mediagoblin.init import (get_jinja_loader, get_staticdirector,
30 setup_global_and_app_config, setup_workbench, setup_database,
31 setup_storage, setup_beaker_cache)
32
33
34 class MediaGoblinApp(object):
35 """
36 WSGI application of MediaGoblin
37
38 ... this is the heart of the program!
39 """
40 def __init__(self, config_path, setup_celery=True):
41 """
42 Initialize the application based on a configuration file.
43
44 Arguments:
45 - config_path: path to the configuration file we're opening.
46 - setup_celery: whether or not to setup celery during init.
47 (Note: setting 'celery_setup_elsewhere' also disables
48 setting up celery.)
49 """
50 ##############
51 # Setup config
52 ##############
53
54 # Open and setup the config
55 global_config, app_config = setup_global_and_app_config(config_path)
56
57 ##########################################
58 # Setup other connections / useful objects
59 ##########################################
60
61 # Set up the database
62 self.connection, self.db = setup_database()
63
64 # Get the template environment
65 self.template_loader = get_jinja_loader(
66 app_config.get('local_templates'))
67
68 # Set up storage systems
69 self.public_store, self.queue_store = setup_storage()
70
71 # set up routing
72 self.routing = routing.get_mapper()
73
74 # set up staticdirector tool
75 self.staticdirector = get_staticdirector(app_config)
76
77 # set up caching
78 self.cache = setup_beaker_cache()
79
80 # Setup celery, if appropriate
81 if setup_celery and not app_config.get('celery_setup_elsewhere'):
82 if os.environ.get('CELERY_ALWAYS_EAGER'):
83 setup_celery_from_config(
84 app_config, global_config,
85 force_celery_always_eager=True)
86 else:
87 setup_celery_from_config(app_config, global_config)
88
89 #######################################################
90 # Insert appropriate things into mediagoblin.mg_globals
91 #
92 # certain properties need to be accessed globally eg from
93 # validators, etc, which might not access to the request
94 # object.
95 #######################################################
96
97 setup_globals(app=self)
98
99 # Workbench *currently* only used by celery, so this only
100 # matters in always eager mode :)
101 setup_workbench()
102
103 # instantiate application meddleware
104 self.meddleware = [common.import_component(m)(self)
105 for m in meddleware.ENABLED_MEDDLEWARE]
106
107 def __call__(self, environ, start_response):
108 request = Request(environ)
109
110 ## Routing / controller loading stuff
111 path_info = request.path_info
112 route_match = self.routing.match(path_info)
113
114 # By using fcgi, mediagoblin can run under a base path
115 # like /mediagoblin/. request.path_info contains the
116 # path inside mediagoblin. If the something needs the
117 # full path of the current page, that should include
118 # the basepath.
119 # Note: urlgen and routes are fine!
120 request.full_path = environ["SCRIPT_NAME"] + request.path_info
121 # python-routes uses SCRIPT_NAME. So let's use that too.
122 # The other option would be:
123 # request.full_path = environ["SCRIPT_URL"]
124
125 # Fix up environ for urlgen
126 # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
127 if environ.get('HTTPS', '').lower() == 'off':
128 environ.pop('HTTPS')
129
130 ## Attach utilities to the request object
131 request.matchdict = route_match
132 request.urlgen = routes.URLGenerator(self.routing, environ)
133 # Do we really want to load this via middleware? Maybe?
134 request.session = request.environ['beaker.session']
135 # Attach self as request.app
136 # Also attach a few utilities from request.app for convenience?
137 request.app = self
138 request.locale = translate.get_locale_from_request(request)
139
140 request.template_env = template.get_jinja_env(
141 self.template_loader, request.locale)
142 request.db = self.db
143 request.staticdirect = self.staticdirector
144
145 mg_request.setup_user_in_request(request)
146
147 # No matching page?
148 if route_match is None:
149 # Try to do see if we have a match with a trailing slash
150 # added and if so, redirect
151 if not path_info.endswith('/') \
152 and request.method == 'GET' \
153 and self.routing.match(path_info + '/'):
154 new_path_info = path_info + '/'
155 if request.GET:
156 new_path_info = '%s?%s' % (
157 new_path_info, urllib.urlencode(request.GET))
158 redirect = exc.HTTPFound(location=new_path_info)
159 return request.get_response(redirect)(environ, start_response)
160
161 # Okay, no matches. 404 time!
162 request.matchdict = {} # in case our template expects it
163 return render_404(request)(environ, start_response)
164
165 controller = common.import_component(route_match['controller'])
166
167 # pass the request through our meddleware classes
168 for m in self.meddleware:
169 response = m.process_request(request, controller)
170 if response is not None:
171 return response(environ, start_response)
172
173 request.start_response = start_response
174
175 # get the response from the controller
176 response = controller(request)
177
178 # pass the response through the meddleware
179 for m in self.meddleware[::-1]:
180 m.process_response(request, response)
181
182 return response(environ, start_response)
183
184
185 def paste_app_factory(global_config, **app_config):
186 configs = app_config['config'].split()
187 mediagoblin_config = None
188 for config in configs:
189 if os.path.exists(config) and os.access(config, os.R_OK):
190 mediagoblin_config = config
191 break
192
193 if not mediagoblin_config:
194 raise IOError("Usable mediagoblin config not found.")
195
196 mgoblin_app = MediaGoblinApp(mediagoblin_config)
197
198 return mgoblin_app