mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Parse Accept-Language header
This commit is contained in:
parent
bf0909669d
commit
48b6e3d42f
@ -154,6 +154,7 @@ class HTTPRequest(Connection):
|
||||
|
||||
request_handler = None
|
||||
static_cache = None
|
||||
translator_cache = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Connection.__init__(self, *args, **kwargs)
|
||||
|
@ -19,7 +19,9 @@ from calibre.srv.loop import WRITE
|
||||
from calibre.srv.errors import HTTPSimpleResponse
|
||||
from calibre.srv.http_request import HTTPRequest, read_headers
|
||||
from calibre.srv.sendfile import file_metadata, sendfile_to_socket_async, CannotSendfile, SendfileInterrupted
|
||||
from calibre.srv.utils import MultiDict, http_date, HTTP1, HTTP11, socket_errors_socket_closed
|
||||
from calibre.srv.utils import (
|
||||
MultiDict, http_date, HTTP1, HTTP11, socket_errors_socket_closed,
|
||||
sort_q_values, get_translator_for_lang)
|
||||
from calibre.utils.monotonic import monotonic
|
||||
|
||||
Range = namedtuple('Range', 'start stop size')
|
||||
@ -71,21 +73,19 @@ def parse_if_none_match(val): # {{{
|
||||
# }}}
|
||||
|
||||
def acceptable_encoding(val, allowed=frozenset({'gzip'})): # {{{
|
||||
def enc(x):
|
||||
e, r = x.partition(';')[::2]
|
||||
p, v = r.partition('=')[::2]
|
||||
q = 1.0
|
||||
if p == 'q' and v:
|
||||
try:
|
||||
q = float(v)
|
||||
except Exception:
|
||||
pass
|
||||
return e.lower(), q
|
||||
for x in sort_q_values(val):
|
||||
x = x.lower()
|
||||
if x in allowed:
|
||||
return x
|
||||
# }}}
|
||||
|
||||
emap = dict(enc(x.strip()) for x in val.split(','))
|
||||
acceptable = sorted(set(emap) & allowed, key=emap.__getitem__, reverse=True)
|
||||
if acceptable:
|
||||
return acceptable[0]
|
||||
def preferred_lang(val, get_translator_for_lang): # {{{
|
||||
for x in sort_q_values(val):
|
||||
x = x.lower()
|
||||
found, lang, translator = get_translator_for_lang(x)
|
||||
if found:
|
||||
return x
|
||||
return 'en'
|
||||
# }}}
|
||||
|
||||
def get_ranges(headervalue, content_length): # {{{
|
||||
@ -180,9 +180,13 @@ def get_range_parts(ranges, content_type, content_length): # {{{
|
||||
|
||||
class RequestData(object): # {{{
|
||||
|
||||
def __init__(self, method, path, query, inheaders, request_body_file, outheaders, response_protocol, static_cache, opts, remote_addr, remote_port):
|
||||
self.method, self.path, self.query, self.inheaders, self.request_body_file, self.outheaders, self.response_protocol, self.static_cache = (
|
||||
method, path, query, inheaders, request_body_file, outheaders, response_protocol, static_cache
|
||||
def __init__(self, method, path, query, inheaders, request_body_file, outheaders, response_protocol,
|
||||
static_cache, opts, remote_addr, remote_port, translator_cache):
|
||||
|
||||
(self.method, self.path, self.query, self.inheaders, self.request_body_file, self.outheaders,
|
||||
self.response_protocol, self.static_cache, self.translator_cache) = (
|
||||
method, path, query, inheaders, request_body_file, outheaders,
|
||||
response_protocol, static_cache, translator_cache
|
||||
)
|
||||
self.remote_addr, self.remote_port = remote_addr, remote_port
|
||||
self.opts = opts
|
||||
@ -196,6 +200,12 @@ class RequestData(object): # {{{
|
||||
|
||||
def read(self, size=-1):
|
||||
return self.request_body_file.read(size)
|
||||
|
||||
def get_translator(self, bcp_47_code):
|
||||
return get_translator_for_lang(self.translator_cache, bcp_47_code)
|
||||
|
||||
def get_preferred_language(self):
|
||||
return preferred_lang(self.outheaders.get('Accept-Language'), self.get_translator)
|
||||
# }}}
|
||||
|
||||
class ReadableOutput(object):
|
||||
@ -322,7 +332,7 @@ class HTTPConnection(HTTPRequest):
|
||||
data = RequestData(
|
||||
self.method, self.path, self.query, inheaders, request_body_file,
|
||||
outheaders, self.response_protocol, self.static_cache, self.opts,
|
||||
self.remote_addr, self.remote_port
|
||||
self.remote_addr, self.remote_port, self.translator_cache
|
||||
)
|
||||
self.queue_job(self.run_request_handler, data)
|
||||
|
||||
@ -545,10 +555,12 @@ class HTTPConnection(HTTPRequest):
|
||||
|
||||
def create_http_handler(handler):
|
||||
static_cache = {}
|
||||
translator_cache = {}
|
||||
@wraps(handler)
|
||||
def wrapper(*args, **kwargs):
|
||||
ans = HTTPConnection(*args, **kwargs)
|
||||
ans.request_handler = handler
|
||||
ans.static_cache = static_cache
|
||||
ans.translator_cache = translator_cache
|
||||
return ans
|
||||
return wrapper
|
||||
|
@ -66,6 +66,18 @@ class TestHTTP(BaseTest):
|
||||
test('Priority', '1;q=0.5, 2;q=0.75, 3;q=1.0', '3', {'1', '2', '3'})
|
||||
# }}}
|
||||
|
||||
def test_accept_language(self): # {{{
|
||||
'Test parsing of Accept-Language'
|
||||
from calibre.srv.http_response import preferred_lang
|
||||
def test(name, val, ans):
|
||||
self.ae(preferred_lang(val, lambda x:(True, x, None)), ans, name + ' failed')
|
||||
test('Empty field', '', 'en')
|
||||
test('Simple', 'de', 'de')
|
||||
test('Case insensitive', 'Es', 'es')
|
||||
test('Multiple', 'fr, es', 'fr')
|
||||
test('Priority', 'en;q=0.1, de;q=0.7, fr;q=0.5', 'de')
|
||||
# }}}
|
||||
|
||||
def test_range_parsing(self): # {{{
|
||||
'Test parsing of Range header'
|
||||
from calibre.srv.http_response import get_ranges
|
||||
|
@ -12,10 +12,12 @@ from urlparse import parse_qs
|
||||
import repr as reprlib
|
||||
from email.utils import formatdate
|
||||
from operator import itemgetter
|
||||
from future_builtins import map
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import iswindows
|
||||
from calibre.utils.filenames import atomic_rename
|
||||
from calibre.utils.localization import get_translator
|
||||
from calibre.utils.socket_inheritance import set_socket_inherit
|
||||
from calibre.utils.logging import ThreadSafeLog
|
||||
|
||||
@ -169,6 +171,22 @@ def create_sock_pair(port=0):
|
||||
|
||||
return client_sock, srv_sock
|
||||
|
||||
def sort_q_values(header_val):
|
||||
'Get sorted items from an HTTP header of type: a;q=0.5, b;q=0.7...'
|
||||
if not header_val:
|
||||
return []
|
||||
def item(x):
|
||||
e, r = x.partition(';')[::2]
|
||||
p, v = r.partition('=')[::2]
|
||||
q = 1.0
|
||||
if p == 'q' and v:
|
||||
try:
|
||||
q = max(0.0, min(1.0, float(v.strip())))
|
||||
except Exception:
|
||||
pass
|
||||
return e.strip(), q
|
||||
return tuple(map(itemgetter(0), sorted(map(item, header_val.split(',')), key=itemgetter(1), reverse=True)))
|
||||
|
||||
def eintr_retry_call(func, *args, **kwargs):
|
||||
while True:
|
||||
try:
|
||||
@ -178,6 +196,14 @@ def eintr_retry_call(func, *args, **kwargs):
|
||||
continue
|
||||
raise
|
||||
|
||||
def get_translator_for_lang(cache, bcp_47_code):
|
||||
try:
|
||||
return cache[bcp_47_code]
|
||||
except KeyError:
|
||||
pass
|
||||
cache[bcp_47_code] = ans = get_translator(bcp_47_code)
|
||||
return ans
|
||||
|
||||
# Logging {{{
|
||||
|
||||
class ServerLog(ThreadSafeLog):
|
||||
|
@ -107,6 +107,30 @@ def get_all_translators():
|
||||
buf = cStringIO.StringIO(zf.read(mpath + '/messages.mo'))
|
||||
yield lang, GNUTranslations(buf)
|
||||
|
||||
def get_single_translator(mpath):
|
||||
from zipfile import ZipFile
|
||||
with ZipFile(P('localization/locales.zip', allow_user_override=False), 'r') as zf:
|
||||
buf = cStringIO.StringIO(zf.read(mpath + '/messages.mo'))
|
||||
return GNUTranslations(buf)
|
||||
|
||||
def get_translator(bcp_47_code):
|
||||
parts = bcp_47_code.replace('-', '_').split('_')[:2]
|
||||
parts[0] = lang_as_iso639_1(parts[0].lower())
|
||||
if len(parts) > 1:
|
||||
parts[1] = parts[1].upper()
|
||||
lang = '_'.join(parts)
|
||||
lang = {'pt':'pt_BR', 'zh':'zh_CN'}.get(lang, lang)
|
||||
available = available_translations()
|
||||
found = True
|
||||
if lang not in available:
|
||||
lang = {'pt':'pt_BR', 'zh':'zh_CN'}.get(parts[0], parts[0])
|
||||
if lang not in available:
|
||||
lang = get_lang()
|
||||
found = False
|
||||
if lang == 'en':
|
||||
return found, lang, NullTranslations()
|
||||
return found, lang, get_single_translator(lang)
|
||||
|
||||
lcdata = {
|
||||
u'abday': (u'Sun', u'Mon', u'Tue', u'Wed', u'Thu', u'Fri', u'Sat'),
|
||||
u'abmon': (u'Jan', u'Feb', u'Mar', u'Apr', u'May', u'Jun', u'Jul', u'Aug', u'Sep', u'Oct', u'Nov', u'Dec'),
|
||||
|
Loading…
x
Reference in New Issue
Block a user