mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -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
|
request_handler = None
|
||||||
static_cache = None
|
static_cache = None
|
||||||
|
translator_cache = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Connection.__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.errors import HTTPSimpleResponse
|
||||||
from calibre.srv.http_request import HTTPRequest, read_headers
|
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.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
|
from calibre.utils.monotonic import monotonic
|
||||||
|
|
||||||
Range = namedtuple('Range', 'start stop size')
|
Range = namedtuple('Range', 'start stop size')
|
||||||
@ -71,21 +73,19 @@ def parse_if_none_match(val): # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def acceptable_encoding(val, allowed=frozenset({'gzip'})): # {{{
|
def acceptable_encoding(val, allowed=frozenset({'gzip'})): # {{{
|
||||||
def enc(x):
|
for x in sort_q_values(val):
|
||||||
e, r = x.partition(';')[::2]
|
x = x.lower()
|
||||||
p, v = r.partition('=')[::2]
|
if x in allowed:
|
||||||
q = 1.0
|
return x
|
||||||
if p == 'q' and v:
|
# }}}
|
||||||
try:
|
|
||||||
q = float(v)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return e.lower(), q
|
|
||||||
|
|
||||||
emap = dict(enc(x.strip()) for x in val.split(','))
|
def preferred_lang(val, get_translator_for_lang): # {{{
|
||||||
acceptable = sorted(set(emap) & allowed, key=emap.__getitem__, reverse=True)
|
for x in sort_q_values(val):
|
||||||
if acceptable:
|
x = x.lower()
|
||||||
return acceptable[0]
|
found, lang, translator = get_translator_for_lang(x)
|
||||||
|
if found:
|
||||||
|
return x
|
||||||
|
return 'en'
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def get_ranges(headervalue, content_length): # {{{
|
def get_ranges(headervalue, content_length): # {{{
|
||||||
@ -180,9 +180,13 @@ def get_range_parts(ranges, content_type, content_length): # {{{
|
|||||||
|
|
||||||
class RequestData(object): # {{{
|
class RequestData(object): # {{{
|
||||||
|
|
||||||
def __init__(self, method, path, query, inheaders, request_body_file, outheaders, response_protocol, static_cache, opts, remote_addr, remote_port):
|
def __init__(self, method, path, query, inheaders, request_body_file, outheaders, response_protocol,
|
||||||
self.method, self.path, self.query, self.inheaders, self.request_body_file, self.outheaders, self.response_protocol, self.static_cache = (
|
static_cache, opts, remote_addr, remote_port, translator_cache):
|
||||||
method, path, query, inheaders, request_body_file, outheaders, response_protocol, static_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.remote_addr, self.remote_port = remote_addr, remote_port
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
@ -196,6 +200,12 @@ class RequestData(object): # {{{
|
|||||||
|
|
||||||
def read(self, size=-1):
|
def read(self, size=-1):
|
||||||
return self.request_body_file.read(size)
|
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):
|
class ReadableOutput(object):
|
||||||
@ -322,7 +332,7 @@ class HTTPConnection(HTTPRequest):
|
|||||||
data = RequestData(
|
data = RequestData(
|
||||||
self.method, self.path, self.query, inheaders, request_body_file,
|
self.method, self.path, self.query, inheaders, request_body_file,
|
||||||
outheaders, self.response_protocol, self.static_cache, self.opts,
|
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)
|
self.queue_job(self.run_request_handler, data)
|
||||||
|
|
||||||
@ -545,10 +555,12 @@ class HTTPConnection(HTTPRequest):
|
|||||||
|
|
||||||
def create_http_handler(handler):
|
def create_http_handler(handler):
|
||||||
static_cache = {}
|
static_cache = {}
|
||||||
|
translator_cache = {}
|
||||||
@wraps(handler)
|
@wraps(handler)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
ans = HTTPConnection(*args, **kwargs)
|
ans = HTTPConnection(*args, **kwargs)
|
||||||
ans.request_handler = handler
|
ans.request_handler = handler
|
||||||
ans.static_cache = static_cache
|
ans.static_cache = static_cache
|
||||||
|
ans.translator_cache = translator_cache
|
||||||
return ans
|
return ans
|
||||||
return wrapper
|
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'})
|
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): # {{{
|
def test_range_parsing(self): # {{{
|
||||||
'Test parsing of Range header'
|
'Test parsing of Range header'
|
||||||
from calibre.srv.http_response import get_ranges
|
from calibre.srv.http_response import get_ranges
|
||||||
|
@ -12,10 +12,12 @@ from urlparse import parse_qs
|
|||||||
import repr as reprlib
|
import repr as reprlib
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
from future_builtins import map
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.utils.filenames import atomic_rename
|
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.socket_inheritance import set_socket_inherit
|
||||||
from calibre.utils.logging import ThreadSafeLog
|
from calibre.utils.logging import ThreadSafeLog
|
||||||
|
|
||||||
@ -169,6 +171,22 @@ def create_sock_pair(port=0):
|
|||||||
|
|
||||||
return client_sock, srv_sock
|
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):
|
def eintr_retry_call(func, *args, **kwargs):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -178,6 +196,14 @@ def eintr_retry_call(func, *args, **kwargs):
|
|||||||
continue
|
continue
|
||||||
raise
|
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 {{{
|
# Logging {{{
|
||||||
|
|
||||||
class ServerLog(ThreadSafeLog):
|
class ServerLog(ThreadSafeLog):
|
||||||
|
@ -107,6 +107,30 @@ def get_all_translators():
|
|||||||
buf = cStringIO.StringIO(zf.read(mpath + '/messages.mo'))
|
buf = cStringIO.StringIO(zf.read(mpath + '/messages.mo'))
|
||||||
yield lang, GNUTranslations(buf)
|
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 = {
|
lcdata = {
|
||||||
u'abday': (u'Sun', u'Mon', u'Tue', u'Wed', u'Thu', u'Fri', u'Sat'),
|
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'),
|
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