mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
c70734e52d
@ -1,5 +1,9 @@
|
||||
/* CSS for the mobile version of the content server webpage */
|
||||
|
||||
.body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.navigation table.buttons {
|
||||
width: 100%;
|
||||
}
|
||||
@ -85,4 +89,17 @@ div.navigation {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.data-container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.first-line {
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.second-line {
|
||||
margin-top: 0.75ex;
|
||||
display: block;
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
import datetime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1286242553(BasicNewsRecipe):
|
||||
title = u'CACM'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
needs_subscription = True
|
||||
feeds = [(u'CACM', u'http://cacm.acm.org/magazine.rss')]
|
||||
language = 'en'
|
||||
__author__ = 'jonmisurda'
|
||||
no_stylesheets = True
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['FeatureBox', 'ArticleComments', 'SideColumn', \
|
||||
'LeftColumn', 'RightColumn', 'SiteSearch', 'MainNavBar','more', 'SubMenu', 'inner']})
|
||||
]
|
||||
cover_url_pattern = 'http://cacm.acm.org/magazines/%d/%d'
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open('https://cacm.acm.org/login')
|
||||
br.select_form(nr=1)
|
||||
br['current_member[user]'] = self.username
|
||||
br['current_member[passwd]'] = self.password
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
def get_cover_url(self):
|
||||
now = datetime.datetime.now()
|
||||
|
||||
cover_url = None
|
||||
soup = self.index_to_soup(self.cover_url_pattern % (now.year, now.month))
|
||||
cover_item = soup.find('img',attrs={'alt':'magazine cover image'})
|
||||
if cover_item:
|
||||
cover_url = cover_item['src']
|
||||
return cover_url
|
||||
import datetime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1286242553(BasicNewsRecipe):
|
||||
title = u'CACM'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
needs_subscription = True
|
||||
feeds = [(u'CACM', u'http://cacm.acm.org/magazine.rss')]
|
||||
language = 'en'
|
||||
__author__ = 'jonmisurda'
|
||||
no_stylesheets = True
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['FeatureBox', 'ArticleComments', 'SideColumn', \
|
||||
'LeftColumn', 'RightColumn', 'SiteSearch', 'MainNavBar','more', 'SubMenu', 'inner']})
|
||||
]
|
||||
cover_url_pattern = 'http://cacm.acm.org/magazines/%d/%d'
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open('https://cacm.acm.org/login')
|
||||
br.select_form(nr=1)
|
||||
br['current_member[user]'] = self.username
|
||||
br['current_member[passwd]'] = self.password
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
def get_cover_url(self):
|
||||
now = datetime.datetime.now()
|
||||
|
||||
cover_url = None
|
||||
soup = self.index_to_soup(self.cover_url_pattern % (now.year, now.month))
|
||||
cover_item = soup.find('img',attrs={'alt':'magazine cover image'})
|
||||
if cover_item:
|
||||
cover_url = cover_item['src']
|
||||
return cover_url
|
||||
|
@ -42,7 +42,7 @@ class CYBOOK(USBMS):
|
||||
DELETE_EXTS = ['.mbp', '.dat', '.bin', '_6090.t2b', '.thn']
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
coverdata = getattr(metadata, 'thumbnail', None)
|
||||
if coverdata and coverdata[2]:
|
||||
coverdata = coverdata[2]
|
||||
|
@ -77,7 +77,7 @@ class ALEX(N516):
|
||||
name = os.path.splitext(os.path.basename(file_abspath))[0] + '.png'
|
||||
return os.path.join(base, 'covers', name)
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
from calibre.ebooks import calibre_cover
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
coverdata = getattr(metadata, 'thumbnail', None)
|
||||
@ -129,7 +129,7 @@ class AZBOOKA(ALEX):
|
||||
def can_handle(self, device_info, debug=False):
|
||||
return not is_alex(device_info)
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
pass
|
||||
|
||||
class EB511(USBMS):
|
||||
|
@ -102,7 +102,7 @@ class PDNOVEL(USBMS):
|
||||
DELETE_EXTS = ['.jpg', '.jpeg', '.png']
|
||||
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
coverdata = getattr(metadata, 'thumbnail', None)
|
||||
if coverdata and coverdata[2]:
|
||||
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
|
||||
|
@ -45,7 +45,7 @@ class NOOK(USBMS):
|
||||
DELETE_EXTS = ['.jpg']
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
try:
|
||||
from PIL import Image, ImageDraw
|
||||
Image, ImageDraw
|
||||
|
@ -2,5 +2,11 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
MEDIA_XML = 'database/cache/media.xml'
|
||||
MEDIA_EXT = 'database/cache/cacheExt.xml'
|
||||
|
||||
CACHE_XML = 'Sony Reader/database/cache.xml'
|
||||
CACHE_EXT = 'Sony Reader/database/cacheExt.xml'
|
||||
|
||||
MEDIA_THUMBNAIL = 'database/thumbnail'
|
||||
CACHE_THUMBNAIL = 'Sony Reader/database/thumbnail'
|
||||
|
||||
|
@ -9,10 +9,10 @@ Device driver for the SONY devices
|
||||
import os, time, re
|
||||
|
||||
from calibre.devices.usbms.driver import USBMS, debug_print
|
||||
from calibre.devices.prs505 import MEDIA_XML
|
||||
from calibre.devices.prs505 import CACHE_XML
|
||||
from calibre.devices.prs505 import MEDIA_XML, MEDIA_EXT, CACHE_XML, CACHE_EXT, \
|
||||
MEDIA_THUMBNAIL, CACHE_THUMBNAIL
|
||||
from calibre.devices.prs505.sony_cache import XMLCache
|
||||
from calibre import __appname__
|
||||
from calibre import __appname__, prints
|
||||
from calibre.devices.usbms.books import CollectionsBookList
|
||||
|
||||
class PRS505(USBMS):
|
||||
@ -66,6 +66,8 @@ class PRS505(USBMS):
|
||||
plugboard = None
|
||||
plugboard_func = None
|
||||
|
||||
THUMBNAIL_HEIGHT = 200
|
||||
|
||||
def windows_filter_pnp_id(self, pnp_id):
|
||||
return '_LAUNCHER' in pnp_id
|
||||
|
||||
@ -116,20 +118,21 @@ class PRS505(USBMS):
|
||||
return fname
|
||||
|
||||
def initialize_XML_cache(self):
|
||||
paths, prefixes = {}, {}
|
||||
for prefix, path, source_id in [
|
||||
('main', MEDIA_XML, 0),
|
||||
('card_a', CACHE_XML, 1),
|
||||
('card_b', CACHE_XML, 2)
|
||||
paths, prefixes, ext_paths = {}, {}, {}
|
||||
for prefix, path, ext_path, source_id in [
|
||||
('main', MEDIA_XML, MEDIA_EXT, 0),
|
||||
('card_a', CACHE_XML, CACHE_EXT, 1),
|
||||
('card_b', CACHE_XML, CACHE_EXT, 2)
|
||||
]:
|
||||
prefix = getattr(self, '_%s_prefix'%prefix)
|
||||
if prefix is not None and os.path.exists(prefix):
|
||||
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
||||
ext_paths[source_id] = os.path.join(prefix, *(ext_path.split('/')))
|
||||
prefixes[source_id] = prefix
|
||||
d = os.path.dirname(paths[source_id])
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
return XMLCache(paths, prefixes, self.settings().use_author_sort)
|
||||
return XMLCache(paths, ext_paths, prefixes, self.settings().use_author_sort)
|
||||
|
||||
def books(self, oncard=None, end_session=True):
|
||||
debug_print('PRS505: starting fetching books for card', oncard)
|
||||
@ -174,3 +177,31 @@ class PRS505(USBMS):
|
||||
def set_plugboards(self, plugboards, pb_func):
|
||||
self.plugboards = plugboards
|
||||
self.plugboard_func = pb_func
|
||||
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
if metadata.thumbnail and metadata.thumbnail[-1]:
|
||||
path = path.replace('/', os.sep)
|
||||
is_main = path.startswith(self._main_prefix)
|
||||
thumbnail_dir = MEDIA_THUMBNAIL if is_main else CACHE_THUMBNAIL
|
||||
prefix = None
|
||||
if is_main:
|
||||
prefix = self._main_prefix
|
||||
else:
|
||||
if self._card_a_prefix and \
|
||||
path.startswith(self._card_a_prefix):
|
||||
prefix = self._card_a_prefix
|
||||
elif self._card_b_prefix and \
|
||||
path.startswith(self._card_b_prefix):
|
||||
prefix = self._card_b_prefix
|
||||
if prefix is None:
|
||||
prints('WARNING: Failed to find prefix for:', filepath)
|
||||
return
|
||||
thumbnail_dir = os.path.join(prefix, *thumbnail_dir.split('/'))
|
||||
|
||||
relpath = os.path.relpath(filepath, prefix)
|
||||
thumbnail_dir = os.path.join(thumbnail_dir, relpath)
|
||||
if not os.path.exists(thumbnail_dir):
|
||||
os.makedirs(thumbnail_dir)
|
||||
with open(os.path.join(thumbnail_dir, 'main_thumbnail.jpg'), 'wb') as f:
|
||||
f.write(metadata.thumbnail[-1])
|
||||
|
||||
|
@ -9,6 +9,7 @@ import os, time
|
||||
from base64 import b64decode
|
||||
from uuid import uuid4
|
||||
from lxml import etree
|
||||
from datetime import date
|
||||
|
||||
from calibre import prints, guess_type, isbytestring
|
||||
from calibre.devices.errors import DeviceError
|
||||
@ -18,6 +19,20 @@ from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.ebooks.metadata import authors_to_string, title_sort, \
|
||||
authors_to_sort_string
|
||||
|
||||
'''
|
||||
cahceExt.xml
|
||||
|
||||
Periodical identifier sample from a PRS-650:
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cacheExt xmlns="http://www.sony.com/xmlns/product/prs/device/1">
|
||||
<text conformsTo="http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0" periodicalName="The Atlantic" description="Current affairs and politics focussed on the US" publicationDate="Tue, 19 Oct 2010 00:00:00 GMT" path="database/media/books/calibre/Atlantic [Mon, 18 Oct 2010], The - calibre_1701.epub">
|
||||
<thumbnail width="167" height="217">main_thumbnail.jpg</thumbnail>
|
||||
</text>
|
||||
</cacheExt>
|
||||
|
||||
'''
|
||||
|
||||
# Utility functions {{{
|
||||
EMPTY_CARD_CACHE = '''\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -25,6 +40,12 @@ EMPTY_CARD_CACHE = '''\
|
||||
</cache>
|
||||
'''
|
||||
|
||||
EMPTY_EXT_CACHE = '''\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cacheExt xmlns="http://www.sony.com/xmlns/product/prs/device/1">
|
||||
</cacheExt>
|
||||
'''
|
||||
|
||||
MIME_MAP = {
|
||||
"lrf" : "application/x-sony-bbeb",
|
||||
'lrx' : 'application/x-sony-bbeb',
|
||||
@ -63,7 +84,7 @@ def uuid():
|
||||
|
||||
class XMLCache(object):
|
||||
|
||||
def __init__(self, paths, prefixes, use_author_sort):
|
||||
def __init__(self, paths, ext_paths, prefixes, use_author_sort):
|
||||
if DEBUG:
|
||||
debug_print('Building XMLCache...', paths)
|
||||
self.paths = paths
|
||||
@ -76,8 +97,8 @@ class XMLCache(object):
|
||||
for source_id, path in paths.items():
|
||||
if source_id == 0:
|
||||
if not os.path.exists(path):
|
||||
raise DeviceError('The SONY XML cache media.xml does not exist. Try'
|
||||
' disconnecting and reconnecting your reader.')
|
||||
raise DeviceError(('The SONY XML cache %r does not exist. Try'
|
||||
' disconnecting and reconnecting your reader.')%repr(path))
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
else:
|
||||
@ -85,14 +106,34 @@ class XMLCache(object):
|
||||
if os.access(path, os.R_OK):
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
|
||||
self.roots[source_id] = etree.fromstring(xml_to_unicode(
|
||||
raw, strip_encoding_pats=True, assume_utf8=True,
|
||||
verbose=DEBUG)[0],
|
||||
parser=parser)
|
||||
if self.roots[source_id] is None:
|
||||
raise Exception(('The SONY database at %s is corrupted. Try '
|
||||
raise Exception(('The SONY database at %r is corrupted. Try '
|
||||
' disconnecting and reconnecting your reader.')%path)
|
||||
|
||||
self.ext_paths, self.ext_roots = {}, {}
|
||||
for source_id, path in ext_paths.items():
|
||||
if not os.path.exists(path):
|
||||
try:
|
||||
with open(path, 'wb') as f:
|
||||
f.write(EMPTY_EXT_CACHE)
|
||||
except:
|
||||
pass
|
||||
if os.access(path, os.W_OK):
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
self.ext_roots[source_id] = etree.fromstring(
|
||||
xml_to_unicode(f.read(),
|
||||
strip_encoding_pats=True, assume_utf8=True,
|
||||
verbose=DEBUG)[0], parser=parser)
|
||||
self.ext_paths[source_id] = path
|
||||
except:
|
||||
pass
|
||||
|
||||
# }}}
|
||||
|
||||
recs = self.roots[0].xpath('//*[local-name()="records"]')
|
||||
@ -352,12 +393,18 @@ class XMLCache(object):
|
||||
debug_print('Updating XML Cache:', i)
|
||||
root = self.record_roots[i]
|
||||
lpath_map = self.build_lpath_map(root)
|
||||
ext_root = self.ext_roots[i] if i in self.ext_roots else None
|
||||
ext_lpath_map = None
|
||||
if ext_root is not None:
|
||||
ext_lpath_map = self.build_lpath_map(ext_root)
|
||||
gtz_count = ltz_count = 0
|
||||
use_tz_var = False
|
||||
for book in booklist:
|
||||
path = os.path.join(self.prefixes[i], *(book.lpath.split('/')))
|
||||
record = lpath_map.get(book.lpath, None)
|
||||
created = False
|
||||
if record is None:
|
||||
created = True
|
||||
record = self.create_text_record(root, i, book.lpath)
|
||||
if plugboard is not None:
|
||||
newmi = book.deepcopy_metadata()
|
||||
@ -373,6 +420,13 @@ class XMLCache(object):
|
||||
if book.device_collections is None:
|
||||
book.device_collections = []
|
||||
book.device_collections = playlist_map.get(book.lpath, [])
|
||||
|
||||
if created and ext_root is not None and \
|
||||
ext_lpath_map.get(book.lpath, None) is None:
|
||||
ext_record = self.create_ext_text_record(ext_root, i,
|
||||
book.lpath, book.thumbnail)
|
||||
self.periodicalize_book(book, ext_record)
|
||||
|
||||
debug_print('Timezone votes: %d GMT, %d LTZ, use_tz_var=%s'%
|
||||
(gtz_count, ltz_count, use_tz_var))
|
||||
self.update_playlists(i, root, booklist, collections_attributes)
|
||||
@ -386,6 +440,47 @@ class XMLCache(object):
|
||||
self.fix_ids()
|
||||
debug_print('Finished update')
|
||||
|
||||
def is_sony_periodical(self, book):
|
||||
if _('News') not in book.tags:
|
||||
return False
|
||||
if not book.lpath.lower().endswith('.epub'):
|
||||
return False
|
||||
if book.pubdate.date() < date(2010, 10, 17):
|
||||
return False
|
||||
return True
|
||||
|
||||
def periodicalize_book(self, book, record):
|
||||
if not self.is_sony_periodical(book):
|
||||
return
|
||||
record.set('conformsTo',
|
||||
"http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0")
|
||||
|
||||
record.set('description', '')
|
||||
|
||||
name = None
|
||||
if '[' in book.title:
|
||||
name = book.title.split('[')[0].strip()
|
||||
if len(name) < 4:
|
||||
name = None
|
||||
if not name:
|
||||
try:
|
||||
name = [t for t in book.tags if t != _('News')][0]
|
||||
except:
|
||||
name = None
|
||||
|
||||
if not name:
|
||||
name = book.title
|
||||
|
||||
record.set('periodicalName', name)
|
||||
|
||||
try:
|
||||
pubdate = strftime(book.pubdate.utctimetuple(),
|
||||
zone=lambda x : x)
|
||||
record.set('publicationDate', pubdate)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def rebuild_collections(self, booklist, bl_index):
|
||||
if bl_index not in self.record_roots:
|
||||
return
|
||||
@ -472,6 +567,25 @@ class XMLCache(object):
|
||||
root.append(ans)
|
||||
return ans
|
||||
|
||||
def create_ext_text_record(self, root, bl_id, lpath, thumbnail):
|
||||
namespace = root.nsmap[None]
|
||||
attrib = { 'path': lpath }
|
||||
ans = root.makeelement('{%s}text'%namespace, attrib=attrib,
|
||||
nsmap=root.nsmap)
|
||||
ans.tail = '\n'
|
||||
root[-1].tail = '\n' + '\t'
|
||||
root.append(ans)
|
||||
if thumbnail and thumbnail[-1]:
|
||||
ans.text = '\n' + '\t\t'
|
||||
t = root.makeelement('{%s}thumbnail'%namespace,
|
||||
attrib={'width':str(thumbnail[0]), 'height':str(thumbnail[1])},
|
||||
nsmap=root.nsmap)
|
||||
t.text = 'main_thumbnail.jpg'
|
||||
ans.append(t)
|
||||
t.tail = '\n\t'
|
||||
return ans
|
||||
|
||||
|
||||
def update_text_record(self, record, book, path, bl_index,
|
||||
gtz_count, ltz_count, use_tz_var):
|
||||
'''
|
||||
@ -589,6 +703,18 @@ class XMLCache(object):
|
||||
'<?xml version="1.0" encoding="UTF-8"?>')
|
||||
with open(path, 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
for i, path in self.ext_paths.items():
|
||||
try:
|
||||
raw = etree.tostring(self.ext_roots[i], encoding='UTF-8',
|
||||
xml_declaration=True)
|
||||
except:
|
||||
continue
|
||||
raw = raw.replace("<?xml version='1.0' encoding='UTF-8'?>",
|
||||
'<?xml version="1.0" encoding="UTF-8"?>')
|
||||
with open(path, 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
# }}}
|
||||
|
||||
# Utility methods {{{
|
||||
|
@ -5,8 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import dbus
|
||||
import os
|
||||
import dbus, os
|
||||
|
||||
def node_mountpoint(node):
|
||||
|
||||
@ -56,15 +55,6 @@ class UDisks(object):
|
||||
parent = device_node_path
|
||||
while parent[-1] in '0123456789':
|
||||
parent = parent[:-1]
|
||||
devices = [str(x) for x in self.main.EnumerateDeviceFiles()]
|
||||
for d in devices:
|
||||
if d.startswith(parent) and d != parent:
|
||||
try:
|
||||
self.unmount(d)
|
||||
except:
|
||||
import traceback
|
||||
print 'Failed to unmount:', d
|
||||
traceback.print_exc()
|
||||
d = self.device(parent)
|
||||
d.DriveEject([])
|
||||
|
||||
@ -76,13 +66,19 @@ def eject(node_path):
|
||||
u = UDisks()
|
||||
u.eject(node_path)
|
||||
|
||||
def umount(node_path):
|
||||
u = UDisks()
|
||||
u.unmount(node_path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
dev = sys.argv[1]
|
||||
print 'Testing with node', dev
|
||||
u = UDisks()
|
||||
print 'Mounted at:', u.mount(dev)
|
||||
print 'Ejecting'
|
||||
print 'Unmounting'
|
||||
u.unmount(dev)
|
||||
print 'Ejecting:'
|
||||
u.eject(dev)
|
||||
|
||||
|
||||
|
@ -523,7 +523,8 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
devnodes.append(node)
|
||||
|
||||
devnodes += list(repeat(None, 3))
|
||||
ans = tuple(['/dev/'+x if ok.get(x, False) else None for x in devnodes[:3]])
|
||||
ans = ['/dev/'+x if ok.get(x, False) else None for x in devnodes[:3]]
|
||||
ans.sort(key=lambda x: x[5:] if x else 'zzzzz')
|
||||
return self.linux_swap_drives(ans)
|
||||
|
||||
def linux_swap_drives(self, drives):
|
||||
@ -732,24 +733,36 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
pass
|
||||
|
||||
def eject_linux(self):
|
||||
try:
|
||||
from calibre.devices.udisks import eject
|
||||
return eject(self._linux_main_device_node)
|
||||
except:
|
||||
pass
|
||||
drives = self.find_device_nodes()
|
||||
from calibre.devices.udisks import eject, umount
|
||||
drives = [d for d in self.find_device_nodes() if d]
|
||||
for d in drives:
|
||||
try:
|
||||
umount(d)
|
||||
except:
|
||||
pass
|
||||
failures = False
|
||||
for d in drives:
|
||||
try:
|
||||
eject(d)
|
||||
except Exception, e:
|
||||
print 'Udisks eject call for:', d, 'failed:'
|
||||
print '\t', e
|
||||
failures = True
|
||||
|
||||
if not failures:
|
||||
return
|
||||
|
||||
for drive in drives:
|
||||
if drive:
|
||||
cmd = 'calibre-mount-helper'
|
||||
if getattr(sys, 'frozen_path', False):
|
||||
cmd = os.path.join(sys.frozen_path, cmd)
|
||||
cmd = [cmd, 'eject']
|
||||
mp = getattr(self, "_linux_mount_map", {}).get(drive,
|
||||
'dummy/')[:-1]
|
||||
try:
|
||||
subprocess.Popen(cmd + [drive, mp]).wait()
|
||||
except:
|
||||
pass
|
||||
cmd = 'calibre-mount-helper'
|
||||
if getattr(sys, 'frozen_path', False):
|
||||
cmd = os.path.join(sys.frozen_path, cmd)
|
||||
cmd = [cmd, 'eject']
|
||||
mp = getattr(self, "_linux_mount_map", {}).get(drive,
|
||||
'dummy/')[:-1]
|
||||
try:
|
||||
subprocess.Popen(cmd + [drive, mp]).wait()
|
||||
except:
|
||||
pass
|
||||
|
||||
def eject(self):
|
||||
if islinux:
|
||||
|
@ -186,7 +186,8 @@ class USBMS(CLI, Device):
|
||||
self.put_file(infile, filepath, replace_file=True)
|
||||
try:
|
||||
self.upload_cover(os.path.dirname(filepath),
|
||||
os.path.splitext(os.path.basename(filepath))[0], mdata)
|
||||
os.path.splitext(os.path.basename(filepath))[0],
|
||||
mdata, filepath)
|
||||
except: # Failure to upload cover is not catastrophic
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -197,14 +198,15 @@ class USBMS(CLI, Device):
|
||||
debug_print('USBMS: finished uploading %d books'%(len(files)))
|
||||
return zip(paths, cycle([on_card]))
|
||||
|
||||
def upload_cover(self, path, filename, metadata):
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
'''
|
||||
Upload book cover to the device. Default implementation does nothing.
|
||||
|
||||
:param path: the full path were the associated book is located.
|
||||
:param filename: the name of the book file without the extension.
|
||||
:param path: The full path to the directory where the associated book is located.
|
||||
:param filename: The name of the book file without the extension.
|
||||
:param metadata: metadata belonging to the book. Use metadata.thumbnail
|
||||
for cover
|
||||
:param filepath: The full path to the ebook file
|
||||
|
||||
'''
|
||||
pass
|
||||
|
@ -108,6 +108,27 @@ class EPUBInput(InputFormatPlugin):
|
||||
open('calibre_raster_cover.jpg', 'wb').write(
|
||||
renderer)
|
||||
|
||||
def find_opf(self):
|
||||
def attr(n, attr):
|
||||
for k, v in n.attrib.items():
|
||||
if k.endswith(attr):
|
||||
return v
|
||||
try:
|
||||
with open('META-INF/container.xml') as f:
|
||||
root = etree.fromstring(f.read())
|
||||
for r in root.xpath('//*[local-name()="rootfile"]'):
|
||||
if attr(r, 'media-type') != "application/oebps-package+xml":
|
||||
continue
|
||||
path = attr(r, 'full-path')
|
||||
if not path:
|
||||
continue
|
||||
path = os.path.join(os.getcwdu(), *path.split('/'))
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def convert(self, stream, options, file_ext, log, accelerators):
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre import walk
|
||||
@ -116,12 +137,13 @@ class EPUBInput(InputFormatPlugin):
|
||||
zf = ZipFile(stream)
|
||||
zf.extractall(os.getcwd())
|
||||
encfile = os.path.abspath(os.path.join('META-INF', 'encryption.xml'))
|
||||
opf = None
|
||||
for f in walk(u'.'):
|
||||
if f.lower().endswith('.opf') and '__MACOSX' not in f and \
|
||||
not os.path.basename(f).startswith('.'):
|
||||
opf = os.path.abspath(f)
|
||||
break
|
||||
opf = self.find_opf()
|
||||
if opf is None:
|
||||
for f in walk(u'.'):
|
||||
if f.lower().endswith('.opf') and '__MACOSX' not in f and \
|
||||
not os.path.basename(f).startswith('.'):
|
||||
opf = os.path.abspath(f)
|
||||
break
|
||||
path = getattr(stream, 'name', 'stream')
|
||||
|
||||
if opf is None:
|
||||
|
@ -816,6 +816,10 @@ class SortKeyGenerator(object):
|
||||
if val is None:
|
||||
val = ''
|
||||
val = val.lower()
|
||||
|
||||
elif dt == 'bool':
|
||||
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
||||
|
||||
yield val
|
||||
|
||||
# }}}
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import operator, os, json
|
||||
from binascii import hexlify, unhexlify
|
||||
from urllib import quote
|
||||
from urllib import quote, unquote
|
||||
|
||||
import cherrypy
|
||||
|
||||
@ -482,6 +482,8 @@ class BrowseServer(object):
|
||||
|
||||
@Endpoint(sort_type='list')
|
||||
def browse_matches(self, category=None, cid=None, list_sort=None):
|
||||
if list_sort:
|
||||
list_sort = unquote(list_sort)
|
||||
if not cid:
|
||||
raise cherrypy.HTTPError(404, 'invalid category id: %r'%cid)
|
||||
categories = self.categories_cache()
|
||||
|
@ -112,7 +112,6 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
|
||||
CLASS('thumbnail'))
|
||||
|
||||
data = TD()
|
||||
last = None
|
||||
for fmt in book['formats'].split(','):
|
||||
a = ascii_filename(book['authors'])
|
||||
t = ascii_filename(book['title'])
|
||||
@ -124,9 +123,11 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
|
||||
),
|
||||
CLASS('button'))
|
||||
s.tail = u''
|
||||
last = s
|
||||
data.append(s)
|
||||
|
||||
div = DIV(CLASS('data-container'))
|
||||
data.append(div)
|
||||
|
||||
series = u'[%s - %s]'%(book['series'], book['series_index']) \
|
||||
if book['series'] else ''
|
||||
tags = u'Tags=[%s]'%book['tags'] if book['tags'] else ''
|
||||
@ -137,13 +138,13 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
|
||||
if val:
|
||||
ctext += '%s=[%s] '%tuple(val.split(':#:'))
|
||||
|
||||
text = u'\u202f%s %s by %s - %s - %s %s %s' % (book['title'], series,
|
||||
book['authors'], book['size'], book['timestamp'], tags, ctext)
|
||||
|
||||
if last is None:
|
||||
data.text = text
|
||||
else:
|
||||
last.tail += text
|
||||
first = SPAN(u'\u202f%s %s by %s' % (book['title'], series,
|
||||
book['authors']), CLASS('first-line'))
|
||||
div.append(first)
|
||||
second = SPAN(u'%s - %s %s %s' % ( book['size'],
|
||||
book['timestamp'],
|
||||
tags, ctext), CLASS('second-line'))
|
||||
div.append(second)
|
||||
|
||||
bookt.append(TR(thumbnail, data))
|
||||
# }}}
|
||||
@ -229,7 +230,7 @@ class MobileServer(object):
|
||||
no_tag_count=True)
|
||||
book['title'] = record[FM['title']]
|
||||
for x in ('timestamp', 'pubdate'):
|
||||
book[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]])
|
||||
book[x] = strftime('%b, %Y', record[FM[x]])
|
||||
book['id'] = record[FM['id']]
|
||||
books.append(book)
|
||||
for key in CKEYS:
|
||||
|
Loading…
x
Reference in New Issue
Block a user