mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
Implement /icon
Also use nicer URL scheme for specifying thumbnail sizes
This commit is contained in:
parent
d0703d5ae1
commit
cc3d1596f5
@ -6,9 +6,11 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os, errno
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
from calibre import fit_image
|
||||||
|
from calibre.constants import config_dir
|
||||||
from calibre.db.errors import NoSuchFormat
|
from calibre.db.errors import NoSuchFormat
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre.ebooks.metadata.meta import set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata
|
||||||
@ -21,7 +23,7 @@ from calibre.srv.utils import http_date
|
|||||||
from calibre.utils.config_base import tweaks
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre.utils.date import timestampfromdt
|
from calibre.utils.date import timestampfromdt
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.utils.magick.draw import thumbnail
|
from calibre.utils.magick.draw import thumbnail, Image
|
||||||
|
|
||||||
plugboard_content_server_value = 'content_server'
|
plugboard_content_server_value = 'content_server'
|
||||||
plugboard_content_server_formats = ['epub', 'mobi', 'azw3']
|
plugboard_content_server_formats = ['epub', 'mobi', 'azw3']
|
||||||
@ -123,6 +125,8 @@ def book_fmt(ctx, rd, library_id, db, book_id, fmt):
|
|||||||
|
|
||||||
@endpoint('/static/{+what}', auth_required=False, cache_control=24)
|
@endpoint('/static/{+what}', auth_required=False, cache_control=24)
|
||||||
def static(ctx, rd, what):
|
def static(ctx, rd, what):
|
||||||
|
if not what:
|
||||||
|
raise HTTPNotFound()
|
||||||
base = P('content-server', allow_user_override=False)
|
base = P('content-server', allow_user_override=False)
|
||||||
path = os.path.abspath(os.path.join(base, *what.split('/')))
|
path = os.path.abspath(os.path.join(base, *what.split('/')))
|
||||||
if not path.startswith(base) or ':' in what:
|
if not path.startswith(base) or ':' in what:
|
||||||
@ -131,15 +135,70 @@ def static(ctx, rd, what):
|
|||||||
path = P('content-server/' + path)
|
path = P('content-server/' + path)
|
||||||
try:
|
try:
|
||||||
return lopen(path, 'rb')
|
return lopen(path, 'rb')
|
||||||
except EnvironmentError as e:
|
except EnvironmentError:
|
||||||
if e.errno == errno.EISDIR or os.path.isdir(path):
|
|
||||||
raise HTTPNotFound('Cannot get a directory')
|
|
||||||
raise HTTPNotFound()
|
raise HTTPNotFound()
|
||||||
|
|
||||||
@endpoint('/favicon.png', auth_required=False, cache_control=24)
|
@endpoint('/favicon.png', auth_required=False, cache_control=24)
|
||||||
def favicon(ctx, rd):
|
def favicon(ctx, rd):
|
||||||
return lopen(I('lt.png'), 'rb')
|
return lopen(I('lt.png'), 'rb')
|
||||||
|
|
||||||
|
@endpoint('/icon/{+which}', auth_required=False, cache_control=24)
|
||||||
|
def icon(ctx, rd, which):
|
||||||
|
sz = rd.query.get('sz')
|
||||||
|
if sz != 'full':
|
||||||
|
try:
|
||||||
|
sz = int(rd.query.get('sz', 48))
|
||||||
|
except Exception:
|
||||||
|
sz = 48
|
||||||
|
if which in {'', '_'}:
|
||||||
|
raise HTTPNotFound()
|
||||||
|
if which.startswith('_'):
|
||||||
|
base = os.path.join(config_dir, 'tb_icons')
|
||||||
|
path = os.path.abspath(os.path.join(base, *which[1:].split('/')))
|
||||||
|
if not path.startswith(base) or ':' in which:
|
||||||
|
raise HTTPNotFound('Naughty, naughty!')
|
||||||
|
else:
|
||||||
|
base = P('images', allow_user_override=False)
|
||||||
|
path = os.path.abspath(os.path.join(base, *which.split('/')))
|
||||||
|
if not path.startswith(base) or ':' in which:
|
||||||
|
raise HTTPNotFound('Naughty, naughty!')
|
||||||
|
path = os.path.relpath(path, base).replace(os.sep, '/')
|
||||||
|
path = P('images/' + path)
|
||||||
|
if sz == 'full':
|
||||||
|
try:
|
||||||
|
return lopen(path, 'rb')
|
||||||
|
except EnvironmentError:
|
||||||
|
raise HTTPNotFound()
|
||||||
|
tdir = os.path.join(rd.tdir, 'icons')
|
||||||
|
cached = os.path.join(tdir, '%d-%s.png' % (sz, which))
|
||||||
|
try:
|
||||||
|
return lopen(cached, 'rb')
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
src = lopen(path, 'rb')
|
||||||
|
except EnvironmentError:
|
||||||
|
raise HTTPNotFound()
|
||||||
|
with src:
|
||||||
|
img = Image()
|
||||||
|
img.load(src.read())
|
||||||
|
width, height = img.size
|
||||||
|
scaled, width, height = fit_image(width, height, sz, sz)
|
||||||
|
if scaled:
|
||||||
|
img.size = (width, height)
|
||||||
|
try:
|
||||||
|
ans = lopen(cached, 'w+b')
|
||||||
|
except EnvironmentError:
|
||||||
|
try:
|
||||||
|
os.mkdir(tdir)
|
||||||
|
except EnvironmentError:
|
||||||
|
pass
|
||||||
|
ans = lopen(cached, 'w+b')
|
||||||
|
ans.write(img.export('png'))
|
||||||
|
ans.seek(0)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@endpoint('/get/{what}/{book_id}/{library_id=None}', types={'book_id':int})
|
@endpoint('/get/{what}/{book_id}/{library_id=None}', types={'book_id':int})
|
||||||
def get(ctx, rd, what, book_id, library_id):
|
def get(ctx, rd, what, book_id, library_id):
|
||||||
db = ctx.get_library(library_id)
|
db = ctx.get_library(library_id)
|
||||||
@ -149,11 +208,23 @@ def get(ctx, rd, what, book_id, library_id):
|
|||||||
if not db.has_id(book_id):
|
if not db.has_id(book_id):
|
||||||
raise HTTPNotFound('Book with id %r does not exist' % book_id)
|
raise HTTPNotFound('Book with id %r does not exist' % book_id)
|
||||||
library_id = db.server_library_id # in case library_id was None
|
library_id = db.server_library_id # in case library_id was None
|
||||||
if what == 'thumb' or what.startswith('thumb_'):
|
if what == 'thumb':
|
||||||
try:
|
sz = rd.query.get('sz')
|
||||||
w, h = map(int, what.partition('_')[2].partition('x')[::2])
|
w, h = 60, 80
|
||||||
except Exception:
|
if sz is None:
|
||||||
w, h = 60, 80
|
pass
|
||||||
|
elif sz == 'full':
|
||||||
|
w = h = None
|
||||||
|
elif 'x' in sz:
|
||||||
|
try:
|
||||||
|
w, h = map(int, sz.partition('x')[::2])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
w = h = int(sz)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return cover(ctx, rd, library_id, db, book_id, width=w, height=h)
|
return cover(ctx, rd, library_id, db, book_id, width=w, height=h)
|
||||||
elif what == 'cover':
|
elif what == 'cover':
|
||||||
return cover(ctx, rd, library_id, db, book_id)
|
return cover(ctx, rd, library_id, db, book_id)
|
||||||
|
@ -27,9 +27,10 @@ class ContentTest(LibraryBaseTest):
|
|||||||
self.ae(r.status, httplib.NOT_FOUND)
|
self.ae(r.status, httplib.NOT_FOUND)
|
||||||
self.ae(r.read(), body)
|
self.ae(r.read(), body)
|
||||||
|
|
||||||
missing('/static/missing.xxx')
|
for prefix in ('static', 'icon'):
|
||||||
missing('/static/../out.html', b'Naughty, naughty!')
|
missing('/%s/missing.xxx' % prefix)
|
||||||
missing('/static/C:/out.html', b'Naughty, naughty!')
|
missing('/%s/../out.html' % prefix, b'Naughty, naughty!')
|
||||||
|
missing('/%s/C:/out.html' % prefix, b'Naughty, naughty!')
|
||||||
|
|
||||||
def test_response(r):
|
def test_response(r):
|
||||||
self.assertIn(b'max-age=', r.getheader('Cache-Control'))
|
self.assertIn(b'max-age=', r.getheader('Cache-Control'))
|
||||||
@ -38,12 +39,16 @@ class ContentTest(LibraryBaseTest):
|
|||||||
self.assertIsNotNone(r.getheader('ETag'))
|
self.assertIsNotNone(r.getheader('ETag'))
|
||||||
self.assertIsNotNone(r.getheader('Content-Type'))
|
self.assertIsNotNone(r.getheader('Content-Type'))
|
||||||
|
|
||||||
def test(src, url):
|
def test(src, url, sz=None):
|
||||||
raw = P(src, data=True)
|
raw = P(src, data=True)
|
||||||
conn.request('GET', url)
|
conn.request('GET', url)
|
||||||
r = conn.getresponse()
|
r = conn.getresponse()
|
||||||
self.ae(r.status, httplib.OK)
|
self.ae(r.status, httplib.OK)
|
||||||
self.ae(r.read(), raw)
|
data = r.read()
|
||||||
|
if sz is None:
|
||||||
|
self.ae(data, raw)
|
||||||
|
else:
|
||||||
|
self.ae(sz, identify_data(data)[0])
|
||||||
test_response(r)
|
test_response(r)
|
||||||
conn.request('GET', url, headers={'If-None-Match':r.getheader('ETag')})
|
conn.request('GET', url, headers={'If-None-Match':r.getheader('ETag')})
|
||||||
r = conn.getresponse()
|
r = conn.getresponse()
|
||||||
@ -52,6 +57,9 @@ class ContentTest(LibraryBaseTest):
|
|||||||
|
|
||||||
test('content-server/empty.html', '/static/empty.html')
|
test('content-server/empty.html', '/static/empty.html')
|
||||||
test('images/lt.png', '/favicon.png')
|
test('images/lt.png', '/favicon.png')
|
||||||
|
test('images/lt.png', '/icon/lt.png?sz=full')
|
||||||
|
test('images/lt.png', '/icon/lt.png', sz=48)
|
||||||
|
test('images/lt.png', '/icon/lt.png?sz=16', sz=16)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def test_get(self): # {{{
|
def test_get(self): # {{{
|
||||||
@ -60,8 +68,9 @@ class ContentTest(LibraryBaseTest):
|
|||||||
db = server.handler.router.ctx.get_library()
|
db = server.handler.router.ctx.get_library()
|
||||||
conn = server.connect()
|
conn = server.connect()
|
||||||
|
|
||||||
def get(what, book_id, library_id=None):
|
def get(what, book_id, library_id=None, q=''):
|
||||||
conn.request('GET', '/get/%s/%s' % (what, book_id) + (('/' + library_id) if library_id else ''))
|
q = ('?' + q) if q else q
|
||||||
|
conn.request('GET', '/get/%s/%s' % (what, book_id) + (('/' + library_id) if library_id else '') + q)
|
||||||
r = conn.getresponse()
|
r = conn.getresponse()
|
||||||
return r, r.read()
|
return r, r.read()
|
||||||
|
|
||||||
@ -137,15 +146,15 @@ class ContentTest(LibraryBaseTest):
|
|||||||
r, data = get('thumb', 1)
|
r, data = get('thumb', 1)
|
||||||
self.ae(r.status, httplib.OK)
|
self.ae(r.status, httplib.OK)
|
||||||
self.ae(r.getheader('Used-Cache'), 'yes')
|
self.ae(r.getheader('Used-Cache'), 'yes')
|
||||||
r, data = get('thumb_100x100', 1)
|
r, data = get('thumb', 1, q='sz=100')
|
||||||
self.ae(r.status, httplib.OK)
|
self.ae(r.status, httplib.OK)
|
||||||
self.ae(identify_data(data), (100, 100, 'jpeg'))
|
self.ae(identify_data(data), (100, 100, 'jpeg'))
|
||||||
self.ae(r.getheader('Used-Cache'), 'no')
|
self.ae(r.getheader('Used-Cache'), 'no')
|
||||||
r, data = get('thumb_100x100', 1)
|
r, data = get('thumb', 1, q='sz=100x100')
|
||||||
self.ae(r.status, httplib.OK)
|
self.ae(r.status, httplib.OK)
|
||||||
self.ae(r.getheader('Used-Cache'), 'yes')
|
self.ae(r.getheader('Used-Cache'), 'yes')
|
||||||
db.set_cover({1:I('lt.png', data=True)})
|
db.set_cover({1:I('lt.png', data=True)})
|
||||||
r, data = get('thumb_100x100', 1)
|
r, data = get('thumb', 1, q='sz=100')
|
||||||
self.ae(r.status, httplib.OK)
|
self.ae(r.status, httplib.OK)
|
||||||
self.ae(identify_data(data), (100, 100, 'jpeg'))
|
self.ae(identify_data(data), (100, 100, 'jpeg'))
|
||||||
self.ae(r.getheader('Used-Cache'), 'no')
|
self.ae(r.getheader('Used-Cache'), 'no')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user