Get Books: Update Legimi and CDP store plugins

This commit is contained in:
Kovid Goyal 2015-06-17 07:47:48 +05:30
commit cbc14720d4
3 changed files with 56 additions and 37 deletions

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
store_version = 5 # Needed for dynamic plugin loading store_version = 6 # Needed for dynamic plugin loading
__license__ = 'GPL 3' __license__ = 'GPL 3'
__copyright__ = '2013-2015, Tomasz Długosz <tomek3d@gmail.com>' __copyright__ = '2013-2015, Tomasz Długosz <tomek3d@gmail.com>'
@ -65,11 +65,10 @@ class CdpStore(BasicStoreConfig, StorePlugin):
cover_url = ''.join(data.xpath('.//a[@class="product-image"]/img/@data-src')) cover_url = ''.join(data.xpath('.//a[@class="product-image"]/img/@data-src'))
title = ''.join(data.xpath('.//h3[1]/a/@title')) title = ''.join(data.xpath('.//h3[1]/a/@title'))
price = ''.join(data.xpath('.//span[@class="custom_price"]/text()'))+','+''.join(data.xpath('.//span[@class="custom_price"]/sup/text()')) price = ''.join(data.xpath('.//span[@class="custom_price"]/text()'))+','+''.join(data.xpath('.//span[@class="custom_price"]/sup/text()'))
author = '' author = ''.join(data.xpath('.//div[@class="authors"]/@title'))
formats = '' formats = ''
with closing(br.open( id.strip(), timeout=timeout/4)) as nf: with closing(br.open( id.strip(), timeout=timeout/4)) as nf:
idata = html.fromstring(nf.read()) idata = html.fromstring(nf.read())
author = ', '.join(idata.xpath('.//ul[@class="film-data"]/li[1]/p/text()'))
formats = idata.xpath('//div[@class="product-attributes-container"][2]/ul/li/span/text()')[-1] formats = idata.xpath('//div[@class="product-attributes-container"][2]/ul/li/span/text()')[-1]
counter -= 1 counter -= 1

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function) from __future__ import (unicode_literals, division, absolute_import, print_function)
store_version = 5 # Needed for dynamic plugin loading store_version = 6 # Needed for dynamic plugin loading
__license__ = 'GPL 3' __license__ = 'GPL 3'
__copyright__ = '2011-2014, Tomasz Długosz <tomek3d@gmail.com>' __copyright__ = '2011-2015, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re import re
@ -68,7 +68,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
counter -= 1 counter -= 1
s = SearchResult() s = SearchResult()
s.cover_url = 'http://www.legimi.com/' + cover_url s.cover_url = 'http:' + cover_url
s.title = title.strip() s.title = title.strip()
s.author = author.strip() s.author = author.strip()
s.price = price s.price = price

View File

@ -7,11 +7,12 @@ __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import os import os
from binascii import hexlify
from io import BytesIO from io import BytesIO
from threading import Lock from threading import Lock
from calibre import fit_image from calibre import fit_image
from calibre.constants import config_dir from calibre.constants import config_dir, iswindows
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
@ -22,8 +23,9 @@ from calibre.srv.routes import endpoint, json
from calibre.srv.utils import http_date 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, atomic_rename
from calibre.utils.magick.draw import thumbnail, Image from calibre.utils.magick.draw import thumbnail, Image
from calibre.utils.shared_file import share_open
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']
@ -35,6 +37,7 @@ lock = Lock()
# We cannot store mtimes in the filesystem since some operating systems (OS X) # We cannot store mtimes in the filesystem since some operating systems (OS X)
# have only one second precision for mtimes # have only one second precision for mtimes
mtimes = {} mtimes = {}
rename_counter = 0
def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func, extra_etag_data=''): def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func, extra_etag_data=''):
''' We cannot copy files directly from the library folder to the output ''' We cannot copy files directly from the library folder to the output
@ -42,35 +45,52 @@ def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func
instead we copy out the data from the library folder into a temp folder. We instead we copy out the data from the library folder into a temp folder. We
make sure to only do this copy once, using the previous copy, if there have make sure to only do this copy once, using the previous copy, if there have
been no changes to the data for the file since the last copy. ''' been no changes to the data for the file since the last copy. '''
global rename_counter
# Avoid too many items in a single directory for performance # Avoid too many items in a single directory for performance
base = os.path.join(rd.tdir, 'fcache', (('%x' % book_id)[-3:])) base = os.path.join(rd.tdir, 'fcache', (('%x' % book_id)[-3:]))
if iswindows:
base = '\\\\?\\' + os.path.abspath(base) # Ensure fname is not too long for windows' API
library_id = library_id.replace('\\', '_').replace('/', '_') bname = '%s-%s-%x.%s' % (prefix, library_id, book_id, ext)
bname = '%s-%s-%s.%s' % (prefix, library_id, book_id, ext) if '\\' in bname or '/' in bname:
raise ValueError('File components must not contain path separators')
fname = os.path.join(base, bname) fname = os.path.join(base, bname)
# TODO: Implement locking for this cache
previous_mtime = mtimes.get(bname)
used_cache = 'no' used_cache = 'no'
with lock:
previous_mtime = mtimes.get(bname)
if previous_mtime is None or previous_mtime < mtime: if previous_mtime is None or previous_mtime < mtime:
if previous_mtime is not None:
# File exists and may be open, so we cannot change its
# contents, as that would lead to corrupted downloads in any
# clients that are currently downloading the file.
if iswindows:
# On windows in order to re-use bname, we have to rename it
# before deleting it
rename_counter += 1
dname = os.path.join(base, '_%x' % rename_counter)
atomic_rename(fname, dname)
os.remove(dname)
else:
os.remove(fname)
try: try:
ans = lopen(fname, 'w+b') ans = share_open(fname, 'w+b')
except EnvironmentError: except EnvironmentError:
try: try:
os.makedirs(base) os.makedirs(base)
except EnvironmentError: except EnvironmentError:
pass pass
ans = lopen(fname, 'w+b') ans = share_open(fname, 'w+b')
mtimes[bname] = mtime
copy_func(ans) copy_func(ans)
ans.seek(0) ans.seek(0)
mtimes[bname] = mtime
else: else:
ans = lopen(fname, 'rb') ans = share_open(fname, 'rb')
used_cache = 'yes' used_cache = 'yes'
if ctx.testing: if ctx.testing:
rd.outheaders['Used-Cache'] = used_cache rd.outheaders['Used-Cache'] = used_cache
rd.outheaders['Tempfile'] = hexlify(fname.encode('utf-8'))
return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mtime, extra_etag_data) return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mtime, extra_etag_data)
def cover(ctx, rd, library_id, db, book_id, width=None, height=None): def cover(ctx, rd, library_id, db, book_id, width=None, height=None):
@ -141,13 +161,13 @@ def static(ctx, rd, what):
path = os.path.relpath(path, base).replace(os.sep, '/') path = os.path.relpath(path, base).replace(os.sep, '/')
path = P('content-server/' + path) path = P('content-server/' + path)
try: try:
return lopen(path, 'rb') return share_open(path, 'rb')
except EnvironmentError: except EnvironmentError:
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 share_open(I('lt.png'), 'rb')
@endpoint('/icon/{+which}', auth_required=False, cache_control=24) @endpoint('/icon/{+which}', auth_required=False, cache_control=24)
def icon(ctx, rd, which): def icon(ctx, rd, which):
@ -173,18 +193,18 @@ def icon(ctx, rd, which):
path = P('images/' + path) path = P('images/' + path)
if sz == 'full': if sz == 'full':
try: try:
return lopen(path, 'rb') return share_open(path, 'rb')
except EnvironmentError: except EnvironmentError:
raise HTTPNotFound() raise HTTPNotFound()
with lock: with lock:
tdir = os.path.join(rd.tdir, 'icons') tdir = os.path.join(rd.tdir, 'icons')
cached = os.path.join(tdir, '%d-%s.png' % (sz, which)) cached = os.path.join(tdir, '%d-%s.png' % (sz, which))
try: try:
return lopen(cached, 'rb') return share_open(cached, 'rb')
except EnvironmentError: except EnvironmentError:
pass pass
try: try:
src = lopen(path, 'rb') src = share_open(path, 'rb')
except EnvironmentError: except EnvironmentError:
raise HTTPNotFound() raise HTTPNotFound()
with src: with src:
@ -195,13 +215,13 @@ def icon(ctx, rd, which):
if scaled: if scaled:
img.size = (width, height) img.size = (width, height)
try: try:
ans = lopen(cached, 'w+b') ans = share_open(cached, 'w+b')
except EnvironmentError: except EnvironmentError:
try: try:
os.mkdir(tdir) os.mkdir(tdir)
except EnvironmentError: except EnvironmentError:
pass pass
ans = lopen(cached, 'w+b') ans = share_open(cached, 'w+b')
ans.write(img.export('png')) ans.write(img.export('png'))
ans.seek(0) ans.seek(0)
return ans return ans