autocat3/CherryPyApp.py

321 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python
# -*- mode: python; indent-tabs-mode: nil; -*- coding: utf-8 -*-
"""
CherryPyApp.py
Copyright 2009-2014 by Marcello Perathoner
Distributable under the GNU General Public License Version 3 or newer.
The Project Gutenberg Catalog App Server.
Config and route setup.
"""
from __future__ import unicode_literals
import logging
import logging.handlers # rotating file handler
import os
import time
import traceback
import cherrypy
from cherrypy.process import plugins
import six
from six.moves import builtins
from libgutenberg import GutenbergDatabase
import i18n_tool
# Make translator functions available everywhere. Do this early, at
# least before Genshi starts loading templates.
builtins._ = i18n_tool.ugettext
builtins.__ = i18n_tool.ungettext
import ConnectionPool
import Page
import StartPage
import SuggestionsPage
from SearchPage import BookSearchPage, AuthorSearchPage, SubjectSearchPage, BookshelfSearchPage, \
AuthorPage, SubjectPage, BookshelfPage, AlsoDownloadedPage
from BibrecPage import BibrecPage
import CoverPages
import QRCodePage
import CaptchaPage
import Sitemap
import Formatters
import Timer
plugins.Timer = Timer.TimerPlugin
install_dir = os.path.dirname (os.path.abspath (__file__))
CHERRYPY_CONFIG = os.path.join(install_dir, 'CherryPy.conf')
LOCAL_CONFIG = (
os.path.join(install_dir, 'CherryPy.conf'),
os.path.expanduser('~/.autocat3'), '/etc/autocat3.conf'
)
class MyRoutesDispatcher (cherrypy.dispatch.RoutesDispatcher):
""" Dispatcher that tells us the matched route.
CherryPy makes it hard for us by forgetting the matched route object.
Here we add a 'route_name' parameter, that will tell us the route's name.
"""
def connect (self, name, route, controller, **kwargs):
""" Add a 'route_name' parameter that will tell us the matched route. """
kwargs['route_name'] = name
kwargs.setdefault ('action', 'index')
cherrypy.dispatch.RoutesDispatcher.connect (self, name, route, controller, **kwargs)
def main ():
""" Main function. """
# default config
cherrypy.config.update ({
'uid': 0,
'gid': 0,
'server_name': 'localhost',
'genshi.template_dir': os.path.join (install_dir, 'templates'),
'daemonize': False,
'pidfile': None,
'host': 'localhost',
'host_mobile': 'localhost',
'file_host': 'localhost',
})
config_filename = None
cherrypy.config.update (CHERRYPY_CONFIG)
for config_filename in LOCAL_CONFIG:
try:
cherrypy.config.update (config_filename)
break
except IOError:
pass
# Rotating Logs
#
# Remove the default FileHandlers if present.
error_file = cherrypy.log.error_file
access_file = cherrypy.log.access_file
cherrypy.log.error_file = ""
cherrypy.log.access_file = ""
max_bytes = getattr (cherrypy.log, "rot_max_bytes", 100 * 1024 * 1024)
backup_count = getattr (cherrypy.log, "rot_backup_count", 2)
#print(os.path.abspath(error_file)+": Filehandler cherrypy")
h = logging.handlers.RotatingFileHandler (error_file, 'a', max_bytes, backup_count, 'utf-8')
h.setLevel (logging.DEBUG)
h.setFormatter (cherrypy._cplogging.logfmt)
cherrypy.log.error_log.addHandler (h)
h = logging.handlers.RotatingFileHandler (access_file, 'a', max_bytes, backup_count, 'utf-8')
h.setLevel (logging.DEBUG)
h.setFormatter (cherrypy._cplogging.logfmt)
cherrypy.log.access_log.addHandler (h)
if not cherrypy.config['daemonize']:
ch = logging.StreamHandler ()
ch.setLevel (logging.DEBUG)
ch.setFormatter (cherrypy._cplogging.logfmt)
cherrypy.log.error_log.addHandler (ch)
# continue app init
#
cherrypy.log ('*' * 80, context = 'ENGINE', severity = logging.INFO)
cherrypy.log ("Using config file '%s'." % config_filename,
context = 'ENGINE', severity = logging.INFO)
# after cherrypy.config is parsed
Formatters.init ()
cherrypy.log ("Continuing App Init", context = 'ENGINE', severity = logging.INFO)
cherrypy.log ("Continuing App Init", context = 'ENGINE', severity = logging.INFO)
cherrypy.tools.I18nTool = i18n_tool.I18nTool ()
cherrypy.log ("Continuing App Init", context = 'ENGINE', severity = logging.INFO)
# Used to bust the cache on js and css files. This should be the
# files' mtime, but the files are not stored on the app server.
# This is a `good enough´ replacement though.
t = str (int (time.time ()))
cherrypy.config['css_mtime'] = t
cherrypy.config['js_mtime'] = t
cherrypy.config['all_hosts'] = (
cherrypy.config['host'], cherrypy.config['host_mobile'], cherrypy.config['file_host'])
if hasattr (cherrypy.engine, 'signal_handler'):
cherrypy.engine.signal_handler.subscribe ()
GutenbergDatabase.options.update(cherrypy.config)
cherrypy.engine.pool = plugins.ConnectionPool (
cherrypy.engine, params = GutenbergDatabase.get_connection_params (cherrypy.config))
cherrypy.engine.pool.subscribe ()
plugins.Timer (cherrypy.engine).subscribe ()
cherrypy.log ("Daemonizing", context = 'ENGINE', severity = logging.INFO)
if cherrypy.config['daemonize']:
plugins.Daemonizer (cherrypy.engine).subscribe ()
uid = cherrypy.config['uid']
gid = cherrypy.config['gid']
if uid > 0 or gid > 0:
plugins.DropPrivileges (cherrypy.engine, uid = uid, gid = gid, umask = 0o22).subscribe ()
if cherrypy.config['pidfile']:
pid = plugins.PIDFile (cherrypy.engine, cherrypy.config['pidfile'])
# Write pidfile after privileges are dropped (prio == 77)
# or we will not be able to remove it.
cherrypy.engine.subscribe ('start', pid.start, 78)
cherrypy.engine.subscribe ('exit', pid.exit, 78)
cherrypy.log ("Setting up routes", context = 'ENGINE', severity = logging.INFO)
# setup 'routes' dispatcher
#
# d = cherrypy.dispatch.RoutesDispatcher (full_result = True)
d = MyRoutesDispatcher (full_result = True)
cherrypy.routes_mapper = d.mapper
def check_id (environ, result):
""" Check if id is a valid number. """
try:
return str (int (result['id'])) == result['id']
except:
return False
d.connect ('start', r'/ebooks{.format}/',
controller = StartPage.Start ())
d.connect ('suggest', r'/ebooks/suggest{.format}/',
controller = SuggestionsPage.Suggestions ())
# search pages
d.connect ('search', r'/ebooks/search{.format}/',
controller = BookSearchPage ())
d.connect ('author_search', r'/ebooks/authors/search{.format}/',
controller = AuthorSearchPage ())
d.connect ('subject_search', r'/ebooks/subjects/search{.format}/',
controller = SubjectSearchPage ())
d.connect ('bookshelf_search', r'/ebooks/bookshelves/search{.format}/',
controller = BookshelfSearchPage ())
# 'id' pages
d.connect ('author', r'/ebooks/author/{id:\d+}{.format}',
controller = AuthorPage (), conditions = dict (function = check_id))
d.connect ('subject', r'/ebooks/subject/{id:\d+}{.format}',
controller = SubjectPage (), conditions = dict (function = check_id))
d.connect ('bookshelf', r'/ebooks/bookshelf/{id:\d+}{.format}',
controller = BookshelfPage (), conditions = dict (function = check_id))
d.connect ('also', r'/ebooks/{id:\d+}/also/{.format}',
controller = AlsoDownloadedPage (), conditions = dict (function = check_id))
# bibrec pages
d.connect ('download', r'/ebooks/{id:\d+}/download{.format}',
controller = Page.NullPage (), _static = True)
d.connect ('bibrec', r'/ebooks/{id:\d+}{.format}',
controller = BibrecPage (), conditions = dict (function = check_id))
# legacy compatibility with /ebooks/123.bibrec
d.connect ('bibrec2', r'/ebooks/{id:\d+}.bibrec{.format}',
controller = BibrecPage (), conditions = dict (function = check_id))
d.connect ('cover', r'/covers/{size:small|medium}/{order:latest|popular}/{count}',
controller = CoverPages.CoverPages ())
d.connect ('qrcode', r'/qrcode/',
controller = QRCodePage.QRCodePage ())
d.connect ('iplimit', r'/iplimit/',
controller = Page.NullPage ())
d.connect ('stats', r'/stats/',
controller = Page.NullPage (), _static = True)
d.connect ('honeypot_send', r'/ebooks/send/megaupload/{id:\d+}.{filetype}',
controller = Page.NullPage (), _static = True)
# /w/captcha/question/ so varnish will cache it
d.connect ('captcha.question', r'/w/captcha/question/',
controller = CaptchaPage.QuestionPage ())
d.connect ('captcha.answer', r'/w/captcha/answer/',
controller = CaptchaPage.AnswerPage ())
# sitemap protocol access control requires us to place sitemaps in /ebooks/
d.connect ('sitemap', r'/ebooks/sitemaps/',
controller = Sitemap.SitemapIndex ())
d.connect ('sitemap_index', r'/ebooks/sitemaps/{page:\d+}',
controller = Sitemap.Sitemap ())
if 'dropbox_client_id' in cherrypy.config:
import Dropbox
dropbox = Dropbox.Dropbox ()
cherrypy.log ("Dropbox Client Id: %s" % cherrypy.config['dropbox_client_id'],
context = 'ENGINE', severity = logging.INFO)
d.connect ('dropbox_send', r'/ebooks/send/dropbox/{id:\d+}.{filetype}',
controller = dropbox, conditions = dict (function = check_id))
d.connect ('dropbox_callback', r'/ebooks/send/dropbox/',
controller = dropbox)
if 'gdrive_client_id' in cherrypy.config:
import GDrive
gdrive = GDrive.GDrive ()
cherrypy.log ("GDrive Client Id: %s" % cherrypy.config['gdrive_client_id'],
context = 'ENGINE', severity = logging.INFO)
d.connect ('gdrive_send', r'/ebooks/send/gdrive/{id:\d+}.{filetype}',
controller = gdrive, conditions = dict (function = check_id))
d.connect ('gdrive_callback', r'/ebooks/send/gdrive/',
controller = gdrive)
if 'msdrive_client_id' in cherrypy.config:
import MSDrive
msdrive = MSDrive.MSDrive ()
cherrypy.log ("MSDrive Client Id: %s" % cherrypy.config['msdrive_client_id'],
context = 'ENGINE', severity = logging.INFO)
d.connect ('msdrive_send', r'/ebooks/send/msdrive/{id:\d+}.{filetype}',
controller = msdrive, conditions = dict (function = check_id))
d.connect ('msdrive_callback', r'/ebooks/send/msdrive/',
controller = msdrive)
# start http server
#
cherrypy.log ("Mounting root", context = 'ENGINE', severity = logging.INFO)
app = cherrypy.tree.mount (root = None, config = config_filename)
app.merge ({'/': {'request.dispatch': d}})
return app
if __name__ == '__main__':
main ()
cherrypy.engine.start ()
cherrypy.engine.block ()