Commit | Line | Data |
---|---|---|
8e1e744d | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
cf29e8a8 | 2 | # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. |
e5572c60 ML |
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 | ||
571198c9 | 17 | import os |
ec97c937 | 18 | import logging |
31a8ff42 | 19 | |
7742dcc1 JW |
20 | from mediagoblin.routing import url_map, view_functions, add_route |
21 | ||
f1d06e1d | 22 | from werkzeug.wrappers import Request |
7742dcc1 | 23 | from werkzeug.exceptions import HTTPException, NotFound |
31a8ff42 | 24 | |
7742dcc1 | 25 | from mediagoblin import meddleware, __version__ |
c0022ecc CAW |
26 | from mediagoblin.tools import common, translate, template |
27 | from mediagoblin.tools.response import render_404 | |
828fc630 | 28 | from mediagoblin.tools.theme import register_themes |
152a3bfa | 29 | from mediagoblin.tools import request as mg_request |
6e7ce8d1 | 30 | from mediagoblin.mg_globals import setup_globals |
073b61fe | 31 | from mediagoblin.init.celery import setup_celery_from_config |
29b6f917 | 32 | from mediagoblin.init.plugins import setup_plugins |
50854db0 | 33 | from mediagoblin.init import (get_jinja_loader, get_staticdirector, |
6ef75af5 | 34 | setup_global_and_app_config, setup_locales, setup_workbench, setup_database, |
0533f117 | 35 | setup_storage, setup_beaker_cache) |
05e007c1 | 36 | from mediagoblin.tools.pluginapi import PluginManager |
90e342f9 | 37 | |
31a8ff42 | 38 | |
ec97c937 E |
39 | _log = logging.getLogger(__name__) |
40 | ||
41 | ||
8e1e744d | 42 | class MediaGoblinApp(object): |
31a8ff42 | 43 | """ |
3f5cf663 CAW |
44 | WSGI application of MediaGoblin |
45 | ||
46 | ... this is the heart of the program! | |
31a8ff42 | 47 | """ |
3f5cf663 CAW |
48 | def __init__(self, config_path, setup_celery=True): |
49 | """ | |
50 | Initialize the application based on a configuration file. | |
51 | ||
52 | Arguments: | |
53 | - config_path: path to the configuration file we're opening. | |
54 | - setup_celery: whether or not to setup celery during init. | |
55 | (Note: setting 'celery_setup_elsewhere' also disables | |
56 | setting up celery.) | |
57 | """ | |
ec97c937 | 58 | _log.info("GNU MediaGoblin %s main server starting", __version__) |
3f369674 | 59 | _log.debug("Using config file %s", config_path) |
3f5cf663 CAW |
60 | ############## |
61 | # Setup config | |
62 | ############## | |
63 | ||
64 | # Open and setup the config | |
fe289be4 | 65 | global_config, app_config = setup_global_and_app_config(config_path) |
3f5cf663 CAW |
66 | |
67 | ########################################## | |
68 | # Setup other connections / useful objects | |
69 | ########################################## | |
70 | ||
6ef75af5 SS |
71 | # load all available locales |
72 | setup_locales() | |
73 | ||
29b6f917 WKG |
74 | # Set up plugins -- need to do this early so that plugins can |
75 | # affect startup. | |
76 | _log.info("Setting up plugins.") | |
77 | setup_plugins() | |
78 | ||
3f5cf663 | 79 | # Set up the database |
3f4b5e4a | 80 | self.connection, self.db = setup_database() |
ff94114c | 81 | |
828fc630 | 82 | # Register themes |
975be468 | 83 | self.theme_registry, self.current_theme = register_themes(app_config) |
828fc630 | 84 | |
5afdd7a1 | 85 | # Get the template environment |
42ef819c | 86 | self.template_loader = get_jinja_loader( |
3b47da8e | 87 | app_config.get('local_templates'), |
8545dd50 | 88 | self.current_theme, |
05e007c1 | 89 | PluginManager().get_template_paths() |
8545dd50 | 90 | ) |
0c8a30e6 | 91 | |
5afdd7a1 | 92 | # Set up storage systems |
dccef262 | 93 | self.public_store, self.queue_store = setup_storage() |
5afdd7a1 CAW |
94 | |
95 | # set up routing | |
7742dcc1 JW |
96 | self.url_map = url_map |
97 | ||
98 | for route in PluginManager().get_routes(): | |
d56e8263 | 99 | _log.debug('adding plugin route: {0}'.format(route)) |
7742dcc1 | 100 | add_route(*route) |
31a8ff42 | 101 | |
582c4d5f | 102 | # set up staticdirector tool |
c85c9dc7 | 103 | self.staticdirector = get_staticdirector(app_config) |
3f5cf663 | 104 | |
0533f117 CAW |
105 | # set up caching |
106 | self.cache = setup_beaker_cache() | |
107 | ||
3f5cf663 CAW |
108 | # Setup celery, if appropriate |
109 | if setup_celery and not app_config.get('celery_setup_elsewhere'): | |
d9a31a39 | 110 | if os.environ.get('CELERY_ALWAYS_EAGER', 'false').lower() == 'true': |
3f5cf663 CAW |
111 | setup_celery_from_config( |
112 | app_config, global_config, | |
113 | force_celery_always_eager=True) | |
114 | else: | |
115 | setup_celery_from_config(app_config, global_config) | |
116 | ||
117 | ####################################################### | |
118 | # Insert appropriate things into mediagoblin.mg_globals | |
119 | # | |
df9809c2 CAW |
120 | # certain properties need to be accessed globally eg from |
121 | # validators, etc, which might not access to the request | |
122 | # object. | |
3f5cf663 CAW |
123 | ####################################################### |
124 | ||
243c3843 | 125 | setup_globals(app=self) |
1fd97db3 CAW |
126 | |
127 | # Workbench *currently* only used by celery, so this only | |
128 | # matters in always eager mode :) | |
7664b4db | 129 | setup_workbench() |
df9809c2 | 130 | |
ce5ae8da CAW |
131 | # instantiate application meddleware |
132 | self.meddleware = [common.import_component(m)(self) | |
133 | for m in meddleware.ENABLED_MEDDLEWARE] | |
0c8a30e6 | 134 | |
e824570a | 135 | def call_backend(self, environ, start_response): |
31a8ff42 | 136 | request = Request(environ) |
0c8a30e6 | 137 | |
f1d06e1d JW |
138 | ## Compatibility webob -> werkzeug |
139 | request.GET = request.args | |
f1d06e1d JW |
140 | request.accept = request.accept_mimetypes |
141 | ||
582c4d5f | 142 | ## Routing / controller loading stuff |
7742dcc1 | 143 | map_adapter = self.url_map.bind_to_environ(request.environ) |
31a8ff42 | 144 | |
05788ef4 E |
145 | # By using fcgi, mediagoblin can run under a base path |
146 | # like /mediagoblin/. request.path_info contains the | |
147 | # path inside mediagoblin. If the something needs the | |
148 | # full path of the current page, that should include | |
149 | # the basepath. | |
150 | # Note: urlgen and routes are fine! | |
f1d06e1d | 151 | request.full_path = environ["SCRIPT_NAME"] + request.path |
05788ef4 E |
152 | # python-routes uses SCRIPT_NAME. So let's use that too. |
153 | # The other option would be: | |
154 | # request.full_path = environ["SCRIPT_URL"] | |
155 | ||
871fc591 | 156 | # Fix up environ for urlgen |
d23d4b23 | 157 | # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off |
871fc591 E |
158 | if environ.get('HTTPS', '').lower() == 'off': |
159 | environ.pop('HTTPS') | |
160 | ||
3d0557bf | 161 | ## Attach utilities to the request object |
3d0557bf CAW |
162 | # Do we really want to load this via middleware? Maybe? |
163 | request.session = request.environ['beaker.session'] | |
164 | # Attach self as request.app | |
165 | # Also attach a few utilities from request.app for convenience? | |
166 | request.app = self | |
0c8a30e6 | 167 | |
3d0557bf CAW |
168 | request.db = self.db |
169 | request.staticdirect = self.staticdirector | |
170 | ||
1ec7ff2a JW |
171 | request.locale = translate.get_locale_from_request(request) |
172 | request.template_env = template.get_jinja_env( | |
173 | self.template_loader, request.locale) | |
7742dcc1 JW |
174 | |
175 | def build_proxy(endpoint, **kw): | |
176 | try: | |
177 | qualified = kw.pop('qualified') | |
178 | except KeyError: | |
179 | qualified = False | |
180 | ||
181 | return map_adapter.build( | |
182 | endpoint, | |
183 | values=dict(**kw), | |
184 | force_external=qualified) | |
185 | ||
186 | request.urlgen = build_proxy | |
187 | ||
1ec7ff2a JW |
188 | mg_request.setup_user_in_request(request) |
189 | ||
190 | try: | |
191 | endpoint, url_values = map_adapter.match() | |
192 | request.matchdict = url_values | |
193 | except NotFound as exc: | |
194 | return render_404(request)(environ, start_response) | |
195 | except HTTPException as exc: | |
196 | # Support legacy webob.exc responses | |
197 | return exc(environ, start_response) | |
198 | ||
7742dcc1 | 199 | view_func = view_functions[endpoint] |
31a8ff42 | 200 | |
1ec7ff2a JW |
201 | _log.debug('endpoint: {0} view_func: {1}'.format( |
202 | endpoint, | |
203 | view_func)) | |
204 | ||
7742dcc1 JW |
205 | # import the endpoint, or if it's already a callable, call that |
206 | if isinstance(view_func, unicode) \ | |
207 | or isinstance(view_func, str): | |
208 | controller = common.import_component(view_func) | |
8a0d35e7 | 209 | else: |
7742dcc1 | 210 | controller = view_func |
91cf6738 NY |
211 | |
212 | # pass the request through our meddleware classes | |
213 | for m in self.meddleware: | |
214 | response = m.process_request(request, controller) | |
215 | if response is not None: | |
216 | return response(environ, start_response) | |
217 | ||
31a8ff42 CAW |
218 | request.start_response = start_response |
219 | ||
0c8a30e6 NY |
220 | # get the response from the controller |
221 | response = controller(request) | |
222 | ||
ce5ae8da CAW |
223 | # pass the response through the meddleware |
224 | for m in self.meddleware[::-1]: | |
0c8a30e6 NY |
225 | m.process_response(request, response) |
226 | ||
e824570a E |
227 | return response(environ, start_response) |
228 | ||
229 | def __call__(self, environ, start_response): | |
230 | ## If more errors happen that look like unclean sessions: | |
231 | # self.db.check_session_clean() | |
232 | ||
2bc8ff0d | 233 | try: |
e824570a E |
234 | return self.call_backend(environ, start_response) |
235 | finally: | |
236 | # Reset the sql session, so that the next request | |
237 | # gets a fresh session | |
2bc8ff0d | 238 | self.db.reset_after_request() |
31a8ff42 CAW |
239 | |
240 | ||
5784c4e9 | 241 | def paste_app_factory(global_config, **app_config): |
91903aa6 CAW |
242 | configs = app_config['config'].split() |
243 | mediagoblin_config = None | |
244 | for config in configs: | |
245 | if os.path.exists(config) and os.access(config, os.R_OK): | |
246 | mediagoblin_config = config | |
247 | break | |
248 | ||
249 | if not mediagoblin_config: | |
250 | raise IOError("Usable mediagoblin config not found.") | |
251 | ||
252 | mgoblin_app = MediaGoblinApp(mediagoblin_config) | |
b61874b2 | 253 | |
c4d71564 | 254 | return mgoblin_app |