min=0 makes more sense than min=-1
[mediagoblin.git] / mediagoblin / app.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 Free Software Foundation, Inc
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 urllib
18
19 import routes
20 import mongokit
21 from paste.deploy.converters import asbool, asint
22 from webob import Request, exc
23
24 from mediagoblin import routing, util, models, storage, staticdirect
25 from mediagoblin.globals import setup_globals
26 from mediagoblin.celery_setup import setup_celery_from_config
27
28
29 class Error(Exception): pass
30 class ImproperlyConfigured(Error): pass
31
32
33 class MediaGoblinApp(object):
34 """
35 Really basic wsgi app using routes and WebOb.
36 """
37 def __init__(self, connection, database_path,
38 public_store, queue_store,
39 staticdirector,
40 email_sender_address, email_debug_mode,
41 user_template_path=None):
42 # Get the template environment
43 self.template_loader = util.get_jinja_loader(user_template_path)
44
45 # Set up storage systems
46 self.public_store = public_store
47 self.queue_store = queue_store
48
49 # Set up database
50 self.connection = connection
51 self.db = connection[database_path]
52 models.register_models(connection)
53
54 # set up routing
55 self.routing = routing.get_mapper()
56
57 # set up staticdirector tool
58 self.staticdirector = staticdirector
59
60 # certain properties need to be accessed globally eg from
61 # validators, etc, which might not access to the request
62 # object.
63 setup_globals(
64 email_sender_address=email_sender_address,
65 email_debug_mode=email_debug_mode,
66 db_connection=connection,
67 database=self.db,
68 public_store=self.public_store,
69 queue_store=self.queue_store)
70
71 def __call__(self, environ, start_response):
72 request = Request(environ)
73 path_info = request.path_info
74
75 ## Routing / controller loading stuff
76 route_match = self.routing.match(path_info)
77
78 # No matching page?
79 if route_match is None:
80 # Try to do see if we have a match with a trailing slash
81 # added and if so, redirect
82 if not path_info.endswith('/') \
83 and request.method == 'GET' \
84 and self.routing.match(path_info + '/'):
85 new_path_info = path_info + '/'
86 if request.GET:
87 new_path_info = '%s?%s' % (
88 new_path_info, urllib.urlencode(request.GET))
89 redirect = exc.HTTPFound(location=new_path_info)
90 return request.get_response(redirect)(environ, start_response)
91
92 # Okay, no matches. 404 time!
93 return exc.HTTPNotFound()(environ, start_response)
94
95 controller = util.import_component(route_match['controller'])
96 request.start_response = start_response
97
98 ## Attach utilities to the request object
99 request.matchdict = route_match
100 request.urlgen = routes.URLGenerator(self.routing, environ)
101 # Do we really want to load this via middleware? Maybe?
102 request.session = request.environ['beaker.session']
103 # Attach self as request.app
104 # Also attach a few utilities from request.app for convenience?
105 request.app = self
106 request.locale = util.get_locale_from_request(request)
107
108 request.template_env = util.get_jinja_env(
109 self.template_loader, request.locale)
110 request.db = self.db
111 request.staticdirect = self.staticdirector
112
113 util.setup_user_in_request(request)
114
115 return controller(request)(environ, start_response)
116
117
118 def paste_app_factory(global_config, **app_config):
119 # Get the database connection
120 port = app_config.get('db_port')
121 if port:
122 port = asint(port)
123 connection = mongokit.Connection(
124 app_config.get('db_host'), port)
125
126 # Set up the storage systems.
127 public_store = storage.storage_system_from_paste_config(
128 app_config, 'publicstore')
129 queue_store = storage.storage_system_from_paste_config(
130 app_config, 'queuestore')
131
132 # Set up the staticdirect system
133 if app_config.has_key('direct_remote_path'):
134 staticdirector = staticdirect.RemoteStaticDirect(
135 app_config['direct_remote_path'].strip())
136 elif app_config.has_key('direct_remote_paths'):
137 direct_remote_path_lines = app_config[
138 'direct_remote_paths'].strip().splitlines()
139 staticdirector = staticdirect.MultiRemoteStaticDirect(
140 dict([line.strip().split(' ', 1)
141 for line in direct_remote_path_lines]))
142 else:
143 raise ImproperlyConfigured(
144 "One of direct_remote_path or direct_remote_paths must be provided")
145
146 setup_celery_from_config(app_config, global_config)
147
148 mgoblin_app = MediaGoblinApp(
149 connection, app_config.get('db_name', 'mediagoblin'),
150 public_store=public_store, queue_store=queue_store,
151 staticdirector=staticdirector,
152 email_sender_address=app_config.get(
153 'email_sender_address', 'notice@mediagoblin.example.org'),
154 email_debug_mode=asbool(app_config.get('email_debug_mode')),
155 user_template_path=app_config.get('local_templates'))
156
157 return mgoblin_app