Fixing the airy attribution in the header.
[mediagoblin.git] / mediagoblin / app.py
CommitLineData
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 17import os
31a8ff42 18import urllib
ec97c937 19import logging
31a8ff42 20
31a8ff42 21import routes
f1d06e1d
JW
22from webob import exc
23from werkzeug.wrappers import Request
31a8ff42 24
ec97c937 25from mediagoblin import routing, meddleware, __version__
c0022ecc
CAW
26from mediagoblin.tools import common, translate, template
27from mediagoblin.tools.response import render_404
828fc630 28from mediagoblin.tools.theme import register_themes
152a3bfa 29from mediagoblin.tools import request as mg_request
6e7ce8d1 30from mediagoblin.mg_globals import setup_globals
073b61fe 31from mediagoblin.init.celery import setup_celery_from_config
29b6f917 32from mediagoblin.init.plugins import setup_plugins
50854db0
WKG
33from mediagoblin.init import (get_jinja_loader, get_staticdirector,
34 setup_global_and_app_config, setup_workbench, setup_database,
0533f117 35 setup_storage, setup_beaker_cache)
05e007c1 36from mediagoblin.tools.pluginapi import PluginManager
90e342f9 37
31a8ff42 38
ec97c937
E
39_log = logging.getLogger(__name__)
40
41
8e1e744d 42class 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
29b6f917
WKG
71 # Set up plugins -- need to do this early so that plugins can
72 # affect startup.
73 _log.info("Setting up plugins.")
74 setup_plugins()
75
3f5cf663 76 # Set up the database
3f4b5e4a 77 self.connection, self.db = setup_database()
ff94114c 78
828fc630 79 # Register themes
975be468 80 self.theme_registry, self.current_theme = register_themes(app_config)
828fc630 81
5afdd7a1 82 # Get the template environment
42ef819c 83 self.template_loader = get_jinja_loader(
3b47da8e 84 app_config.get('local_templates'),
8545dd50 85 self.current_theme,
05e007c1 86 PluginManager().get_template_paths()
8545dd50 87 )
0c8a30e6 88
5afdd7a1 89 # Set up storage systems
dccef262 90 self.public_store, self.queue_store = setup_storage()
5afdd7a1
CAW
91
92 # set up routing
05e007c1 93 self.routing = routing.get_mapper(PluginManager().get_routes())
31a8ff42 94
582c4d5f 95 # set up staticdirector tool
c85c9dc7 96 self.staticdirector = get_staticdirector(app_config)
3f5cf663 97
0533f117
CAW
98 # set up caching
99 self.cache = setup_beaker_cache()
100
3f5cf663
CAW
101 # Setup celery, if appropriate
102 if setup_celery and not app_config.get('celery_setup_elsewhere'):
d9a31a39 103 if os.environ.get('CELERY_ALWAYS_EAGER', 'false').lower() == 'true':
3f5cf663
CAW
104 setup_celery_from_config(
105 app_config, global_config,
106 force_celery_always_eager=True)
107 else:
108 setup_celery_from_config(app_config, global_config)
109
110 #######################################################
111 # Insert appropriate things into mediagoblin.mg_globals
112 #
df9809c2
CAW
113 # certain properties need to be accessed globally eg from
114 # validators, etc, which might not access to the request
115 # object.
3f5cf663
CAW
116 #######################################################
117
243c3843 118 setup_globals(app=self)
1fd97db3
CAW
119
120 # Workbench *currently* only used by celery, so this only
121 # matters in always eager mode :)
7664b4db 122 setup_workbench()
df9809c2 123
ce5ae8da
CAW
124 # instantiate application meddleware
125 self.meddleware = [common.import_component(m)(self)
126 for m in meddleware.ENABLED_MEDDLEWARE]
0c8a30e6 127
e824570a 128 def call_backend(self, environ, start_response):
31a8ff42 129 request = Request(environ)
0c8a30e6 130
f1d06e1d
JW
131 ## Compatibility webob -> werkzeug
132 request.GET = request.args
f1d06e1d
JW
133 request.accept_language = request.accept_languages
134 request.accept = request.accept_mimetypes
135
582c4d5f 136 ## Routing / controller loading stuff
f1d06e1d 137 path_info = request.path
0f63a944 138 route_match = self.routing.match(path_info)
31a8ff42 139
05788ef4
E
140 # By using fcgi, mediagoblin can run under a base path
141 # like /mediagoblin/. request.path_info contains the
142 # path inside mediagoblin. If the something needs the
143 # full path of the current page, that should include
144 # the basepath.
145 # Note: urlgen and routes are fine!
f1d06e1d 146 request.full_path = environ["SCRIPT_NAME"] + request.path
05788ef4
E
147 # python-routes uses SCRIPT_NAME. So let's use that too.
148 # The other option would be:
149 # request.full_path = environ["SCRIPT_URL"]
150
871fc591 151 # Fix up environ for urlgen
d23d4b23 152 # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
871fc591
E
153 if environ.get('HTTPS', '').lower() == 'off':
154 environ.pop('HTTPS')
155
3d0557bf
CAW
156 ## Attach utilities to the request object
157 request.matchdict = route_match
158 request.urlgen = routes.URLGenerator(self.routing, environ)
159 # Do we really want to load this via middleware? Maybe?
160 request.session = request.environ['beaker.session']
161 # Attach self as request.app
162 # Also attach a few utilities from request.app for convenience?
163 request.app = self
ae3bc7fa 164 request.locale = translate.get_locale_from_request(request)
0c8a30e6 165
ae3bc7fa 166 request.template_env = template.get_jinja_env(
3d0557bf
CAW
167 self.template_loader, request.locale)
168 request.db = self.db
169 request.staticdirect = self.staticdirector
170
152a3bfa 171 mg_request.setup_user_in_request(request)
3d0557bf 172
31a8ff42
CAW
173 # No matching page?
174 if route_match is None:
175 # Try to do see if we have a match with a trailing slash
176 # added and if so, redirect
177 if not path_info.endswith('/') \
178 and request.method == 'GET' \
0f63a944 179 and self.routing.match(path_info + '/'):
31a8ff42
CAW
180 new_path_info = path_info + '/'
181 if request.GET:
182 new_path_info = '%s?%s' % (
183 new_path_info, urllib.urlencode(request.GET))
1bb0fdf2 184 redirect = exc.HTTPFound(location=new_path_info)
31a8ff42
CAW
185 return request.get_response(redirect)(environ, start_response)
186
187 # Okay, no matches. 404 time!
3807e8e2 188 request.matchdict = {} # in case our template expects it
c0022ecc 189 return render_404(request)(environ, start_response)
31a8ff42 190
8a0d35e7
CAW
191 # import the controller, or if it's already a callable, call that
192 route_controller = route_match['controller']
193 if isinstance(route_controller, unicode) \
194 or isinstance(route_controller, str):
195 controller = common.import_component(route_match['controller'])
196 else:
197 controller = route_match['controller']
91cf6738
NY
198
199 # pass the request through our meddleware classes
200 for m in self.meddleware:
201 response = m.process_request(request, controller)
202 if response is not None:
203 return response(environ, start_response)
204
31a8ff42
CAW
205 request.start_response = start_response
206
0c8a30e6
NY
207 # get the response from the controller
208 response = controller(request)
209
ce5ae8da
CAW
210 # pass the response through the meddleware
211 for m in self.meddleware[::-1]:
0c8a30e6
NY
212 m.process_response(request, response)
213
e824570a
E
214 return response(environ, start_response)
215
216 def __call__(self, environ, start_response):
217 ## If more errors happen that look like unclean sessions:
218 # self.db.check_session_clean()
219
2bc8ff0d 220 try:
e824570a
E
221 return self.call_backend(environ, start_response)
222 finally:
223 # Reset the sql session, so that the next request
224 # gets a fresh session
2bc8ff0d 225 self.db.reset_after_request()
31a8ff42
CAW
226
227
5784c4e9 228def paste_app_factory(global_config, **app_config):
91903aa6
CAW
229 configs = app_config['config'].split()
230 mediagoblin_config = None
231 for config in configs:
232 if os.path.exists(config) and os.access(config, os.R_OK):
233 mediagoblin_config = config
234 break
235
236 if not mediagoblin_config:
237 raise IOError("Usable mediagoblin config not found.")
238
239 mgoblin_app = MediaGoblinApp(mediagoblin_config)
b61874b2 240
c4d71564 241 return mgoblin_app