Pull from trunk

This commit is contained in:
Kovid Goyal 2009-04-15 14:41:07 -07:00
commit b3e1025c7c
18 changed files with 474 additions and 271 deletions

View File

@ -571,9 +571,6 @@ Condition 08195201-0797-932C-4B51-E5EF9D1D41BD -active Yes -parent 710F2507-2557
Condition 2E18F4AE-F1BB-5C62-2900-73A576A49261 -active Yes -parent 710F2507-2557-652D-EA55-440D710EFDFA -title {String Is Condition} -component StringIsCondition -TreeObject::id 2E18F4AE-F1BB-5C62-2900-73A576A49261 Condition 2E18F4AE-F1BB-5C62-2900-73A576A49261 -active Yes -parent 710F2507-2557-652D-EA55-440D710EFDFA -title {String Is Condition} -component StringIsCondition -TreeObject::id 2E18F4AE-F1BB-5C62-2900-73A576A49261
InstallComponent 21B897C4-24BE-70D1-58EA-DE78EFA60719 -setup Install -type action -conditions 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5 -title {Message Box} -component MessageBox -command insert -active Yes -parent 8A7FD0C2-F053-8764-F204-4BAE71E05708 InstallComponent 21B897C4-24BE-70D1-58EA-DE78EFA60719 -setup Install -type action -conditions 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5 -title {Message Box} -component MessageBox -command insert -active Yes -parent 8A7FD0C2-F053-8764-F204-4BAE71E05708
Condition 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5 -active Yes -parent 21B897C4-24BE-70D1-58EA-DE78EFA60719 -title {String Is Condition} -component StringIsCondition -TreeObject::id 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5 Condition 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5 -active Yes -parent 21B897C4-24BE-70D1-58EA-DE78EFA60719 -title {String Is Condition} -component StringIsCondition -TreeObject::id 76FA3CA2-1F09-75C5-C6CF-72719A8EC4A5
InstallComponent 5D20DD8D-064A-9922-29E1-A7FABEF3666A -setup Install -type action -conditions {E5D227F7-E549-EFA9-1781-EFA6C5EEEC5C A8856922-E6C1-160B-E55C-5C1806A89136} -title {Launch Application Checkbutton} -component AddWidget -command insert -active Yes -parent 8A7FD0C2-F053-8764-F204-4BAE71E05708
Condition E5D227F7-E549-EFA9-1781-EFA6C5EEEC5C -active Yes -parent 5D20DD8D-064A-9922-29E1-A7FABEF3666A -title {File Exists Condition} -component FileExistsCondition -TreeObject::id E5D227F7-E549-EFA9-1781-EFA6C5EEEC5C
Condition A8856922-E6C1-160B-E55C-5C1806A89136 -active Yes -parent 5D20DD8D-064A-9922-29E1-A7FABEF3666A -title {String Is Condition} -component StringIsCondition -TreeObject::id A8856922-E6C1-160B-E55C-5C1806A89136
InstallComponent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -setup Install -type action -conditions {96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0 FBA33088-C809-DD6B-D337-EADBF1CEE966} -title {Desktop Shortcut Checkbutton} -component AddWidget -command insert -active Yes -parent 8A7FD0C2-F053-8764-F204-4BAE71E05708 InstallComponent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -setup Install -type action -conditions {96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0 FBA33088-C809-DD6B-D337-EADBF1CEE966} -title {Desktop Shortcut Checkbutton} -component AddWidget -command insert -active Yes -parent 8A7FD0C2-F053-8764-F204-4BAE71E05708
Condition 96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0 -active Yes -parent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0 Condition 96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0 -active Yes -parent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 96440B8B-C6D0-FCCA-6D3C-7ECE1C304CC0
Condition FBA33088-C809-DD6B-D337-EADBF1CEE966 -active Yes -parent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -title {String Is Condition} -component StringIsCondition -TreeObject::id FBA33088-C809-DD6B-D337-EADBF1CEE966 Condition FBA33088-C809-DD6B-D337-EADBF1CEE966 -active Yes -parent 940F7FED-7D20-7264-3BF9-ED78205A76B3 -title {String Is Condition} -component StringIsCondition -TreeObject::id FBA33088-C809-DD6B-D337-EADBF1CEE966
@ -630,7 +627,7 @@ Condition 03FA7EEF-F626-B69A-09C6-0AA7A54EE9E7 -active Yes -parent E32519F3-A540
InstallComponent D86BBA5C-4903-33BA-59F8-4266A3D45896 -setup Install -type action -conditions {C4C0A903-CF2A-D25A-27AB-A64219FB7E70 5EC7056B-6F90-311E-2C6F-76E96164CFFD} -title {Install Quick Launch Shortcut} -component InstallWindowsShortcut -command insert -active Yes -parent 28BAE662-E103-4E3F-D298-C8FBA36361FC InstallComponent D86BBA5C-4903-33BA-59F8-4266A3D45896 -setup Install -type action -conditions {C4C0A903-CF2A-D25A-27AB-A64219FB7E70 5EC7056B-6F90-311E-2C6F-76E96164CFFD} -title {Install Quick Launch Shortcut} -component InstallWindowsShortcut -command insert -active Yes -parent 28BAE662-E103-4E3F-D298-C8FBA36361FC
Condition C4C0A903-CF2A-D25A-27AB-A64219FB7E70 -active Yes -parent D86BBA5C-4903-33BA-59F8-4266A3D45896 -title {String Is Condition} -component StringIsCondition -TreeObject::id C4C0A903-CF2A-D25A-27AB-A64219FB7E70 Condition C4C0A903-CF2A-D25A-27AB-A64219FB7E70 -active Yes -parent D86BBA5C-4903-33BA-59F8-4266A3D45896 -title {String Is Condition} -component StringIsCondition -TreeObject::id C4C0A903-CF2A-D25A-27AB-A64219FB7E70
Condition 5EC7056B-6F90-311E-2C6F-76E96164CFFD -active Yes -parent D86BBA5C-4903-33BA-59F8-4266A3D45896 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 5EC7056B-6F90-311E-2C6F-76E96164CFFD Condition 5EC7056B-6F90-311E-2C6F-76E96164CFFD -active Yes -parent D86BBA5C-4903-33BA-59F8-4266A3D45896 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 5EC7056B-6F90-311E-2C6F-76E96164CFFD
InstallComponent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -setup Install -type action -conditions {4E5FC4FE-5D37-B216-CFFE-E046A2D6321E E560F3A1-208D-2B4F-2C87-E08595F8E1CD 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475} -title {Launch Application} -component ExecuteExternalProgram -command insert -active Yes -parent 28BAE662-E103-4E3F-D298-C8FBA36361FC InstallComponent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -setup Install -type action -conditions {4E5FC4FE-5D37-B216-CFFE-E046A2D6321E E560F3A1-208D-2B4F-2C87-E08595F8E1CD 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475} -title {Launch Application} -component ExecuteExternalProgram -command insert -active No -parent 28BAE662-E103-4E3F-D298-C8FBA36361FC
Condition 4E5FC4FE-5D37-B216-CFFE-E046A2D6321E -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {String Is Condition} -component StringIsCondition -TreeObject::id 4E5FC4FE-5D37-B216-CFFE-E046A2D6321E Condition 4E5FC4FE-5D37-B216-CFFE-E046A2D6321E -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {String Is Condition} -component StringIsCondition -TreeObject::id 4E5FC4FE-5D37-B216-CFFE-E046A2D6321E
Condition E560F3A1-208D-2B4F-2C87-E08595F8E1CD -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {String Is Condition} -component StringIsCondition -TreeObject::id E560F3A1-208D-2B4F-2C87-E08595F8E1CD Condition E560F3A1-208D-2B4F-2C87-E08595F8E1CD -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {String Is Condition} -component StringIsCondition -TreeObject::id E560F3A1-208D-2B4F-2C87-E08595F8E1CD
Condition 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475 -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475 Condition 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475 -active Yes -parent 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 9C1E4BD9-066D-ABCE-28D0-9E194B9F8475
@ -802,6 +799,9 @@ CreateQuickLaunchShortcut
28FDA3F4-B799-901F-8A27-AA04F0C022AB,Title,subst 28FDA3F4-B799-901F-8A27-AA04F0C022AB,Title,subst
1 1
2A230259-3A6F-8669-8B8B-23C3E7C1BFC2,Active
No
2A230259-3A6F-8669-8B8B-23C3E7C1BFC2,Conditions 2A230259-3A6F-8669-8B8B-23C3E7C1BFC2,Conditions
{3 conditions} {3 conditions}
@ -976,27 +976,6 @@ disabled
5C66451D-6042-DBDE-0D8C-31156EE244AD,Widget 5C66451D-6042-DBDE-0D8C-31156EE244AD,Widget
{Back Button;Next Button} {Back Button;Next Button}
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Background
white
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Conditions
{2 conditions}
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Text,subst
1
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Type
checkbutton
5D20DD8D-064A-9922-29E1-A7FABEF3666A,VirtualText
LaunchApplication
5D20DD8D-064A-9922-29E1-A7FABEF3666A,X
185
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Y
130
5EC7056B-6F90-311E-2C6F-76E96164CFFD,CheckCondition 5EC7056B-6F90-311E-2C6F-76E96164CFFD,CheckCondition
{Before Action is Executed} {Before Action is Executed}
@ -1408,15 +1387,6 @@ disabled
A75C97CC-01AC-C12A-D663-A54E3257F11B,Widget A75C97CC-01AC-C12A-D663-A54E3257F11B,Widget
{Back Button;Next Button} {Back Button;Next Button}
A8856922-E6C1-160B-E55C-5C1806A89136,CheckCondition
{Before Action is Executed}
A8856922-E6C1-160B-E55C-5C1806A89136,Operator
false
A8856922-E6C1-160B-E55C-5C1806A89136,String
<%InstallStopped%>
AAEC34E6-7F02-18F2-30BB-744738192A3B,Conditions AAEC34E6-7F02-18F2-30BB-744738192A3B,Conditions
{2 conditions} {2 conditions}
@ -1730,12 +1700,6 @@ disabled
E5CBB018-A89D-3145-CFF5-CFC3B62BEA97,Widget E5CBB018-A89D-3145-CFF5-CFC3B62BEA97,Widget
{NextButton; CancelButton} {NextButton; CancelButton}
E5D227F7-E549-EFA9-1781-EFA6C5EEEC5C,CheckCondition
{Before Action is Executed}
E5D227F7-E549-EFA9-1781-EFA6C5EEEC5C,Filename
<%ProgramExecutable%>
E611105F-DC85-9E20-4F7B-E63C54E5DF06,Message,subst E611105F-DC85-9E20-4F7B-E63C54E5DF06,Message,subst
1 1
@ -2340,9 +2304,6 @@ Please make sure that calibre is not running, as this will cause the install to
48E8A9D6-B57E-C506-680D-898C65DD2A1B,Title 48E8A9D6-B57E-C506-680D-898C65DD2A1B,Title
<%InstallApplicationText%> <%InstallApplicationText%>
5D20DD8D-064A-9922-29E1-A7FABEF3666A,Text
<%LaunchApplicationText%>
64B8D0F3-4B11-DA22-D6E7-7248872D5FA7,Message 64B8D0F3-4B11-DA22-D6E7-7248872D5FA7,Message
<%UninstallStartupText%> <%UninstallStartupText%>
@ -2356,7 +2317,7 @@ Please make sure that calibre is not running, as this will cause the install to
{<%AppName%> Installation complete} {<%AppName%> Installation complete}
8A7FD0C2-F053-8764-F204-4BAE71E05708,Message 8A7FD0C2-F053-8764-F204-4BAE71E05708,Message
{Installation of <%AppName%> was successful. Click Finish to quit the installer.} {Installation of <%AppName%> was successful. Click Finish to quit the installer. <%AppName%> can be launched from the start menu.}
940F7FED-7D20-7264-3BF9-ED78205A76B3,Text 940F7FED-7D20-7264-3BF9-ED78205A76B3,Text
<%CreateDesktopShortcutText%> <%CreateDesktopShortcutText%>

View File

@ -25,7 +25,7 @@ def get_metadata(stream):
for item in litfile.manifest.values(): for item in litfile.manifest.values():
if item.path in candidates: if item.path in candidates:
try: try:
covers.append((litfile.get_file('/data/'+item.internal), covers.append((litfile.get_file('/data/'+item.internal),
ctype)) ctype))
except: except:
pass pass
@ -33,7 +33,7 @@ def get_metadata(stream):
covers.sort(cmp=lambda x, y:cmp(len(x[0]), len(y[0])), reverse=True) covers.sort(cmp=lambda x, y:cmp(len(x[0]), len(y[0])), reverse=True)
idx = 0 idx = 0
if len(covers) > 1: if len(covers) > 1:
if covers[1][1] == covers[1][0]+'-standard': if covers[1][1] == covers[0][1]+'-standard':
idx = 1 idx = 1
mi.cover_data = ('jpg', covers[idx][0]) mi.cover_data = ('jpg', covers[idx][0])
return mi return mi

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, collections import os, re, collections
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.customize.ui import get_file_type_metadata, set_file_type_metadata from calibre.customize.ui import get_file_type_metadata, set_file_type_metadata
@ -37,18 +37,18 @@ def metadata_from_formats(formats):
mi2 = opf_metadata(opf) mi2 = opf_metadata(opf)
if mi2 is not None and mi2.title: if mi2 is not None and mi2.title:
return mi2 return mi2
for path, ext in zip(formats, extensions): for path, ext in zip(formats, extensions):
with open(path, 'rb') as stream: with open(path, 'rb') as stream:
try: try:
newmi = get_metadata(stream, stream_type=ext, newmi = get_metadata(stream, stream_type=ext,
use_libprs_metadata=True) use_libprs_metadata=True)
mi.smart_update(newmi) mi.smart_update(newmi)
except: except:
continue continue
if getattr(mi, 'application_id', None) is not None: if getattr(mi, 'application_id', None) is not None:
return mi return mi
if not mi.title: if not mi.title:
mi.title = _('Unknown') mi.title = _('Unknown')
if not mi.authors: if not mi.authors:
@ -64,20 +64,20 @@ def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False):
stream_type = 'mobi' stream_type = 'mobi'
if stream_type in ('odt', 'ods', 'odp', 'odg', 'odf'): if stream_type in ('odt', 'ods', 'odp', 'odg', 'odf'):
stream_type = 'odt' stream_type = 'odt'
opf = None opf = None
if hasattr(stream, 'name'): if hasattr(stream, 'name'):
c = os.path.splitext(stream.name)[0]+'.opf' c = os.path.splitext(stream.name)[0]+'.opf'
if os.access(c, os.R_OK): if os.access(c, os.R_OK):
opf = opf_metadata(os.path.abspath(c)) opf = opf_metadata(os.path.abspath(c))
if use_libprs_metadata and getattr(opf, 'application_id', None) is not None: if use_libprs_metadata and getattr(opf, 'application_id', None) is not None:
return opf return opf
mi = MetaInformation(None, None) mi = MetaInformation(None, None)
if prefs['read_file_metadata']: if prefs['read_file_metadata']:
mi = get_file_type_metadata(stream, stream_type) mi = get_file_type_metadata(stream, stream_type)
name = os.path.basename(getattr(stream, 'name', '')) name = os.path.basename(getattr(stream, 'name', ''))
base = metadata_from_filename(name) base = metadata_from_filename(name)
if base.title == os.path.splitext(name)[0] and base.authors is None: if base.title == os.path.splitext(name)[0] and base.authors is None:
@ -98,17 +98,17 @@ def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False):
base.smart_update(mi) base.smart_update(mi)
if opf is not None: if opf is not None:
base.smart_update(opf) base.smart_update(opf)
return base return base
def set_metadata(stream, mi, stream_type='lrf'): def set_metadata(stream, mi, stream_type='lrf'):
if stream_type: if stream_type:
stream_type = stream_type.lower() stream_type = stream_type.lower()
set_file_type_metadata(stream, mi, stream_type) set_file_type_metadata(stream, mi, stream_type)
def metadata_from_filename(name, pat=None): def metadata_from_filename(name, pat=None):
name = os.path.splitext(name)[0] name = name.rpartition('.')[0]
mi = MetaInformation(None, None) mi = MetaInformation(None, None)
if pat is None: if pat is None:
pat = re.compile(prefs.get('filename_pattern')) pat = re.compile(prefs.get('filename_pattern'))
@ -161,7 +161,7 @@ def opf_metadata(opfpath):
mi = MetaInformation(opf) mi = MetaInformation(opf)
if hasattr(opf, 'cover') and opf.cover: if hasattr(opf, 'cover') and opf.cover:
cpath = os.path.join(os.path.dirname(opfpath), opf.cover) cpath = os.path.join(os.path.dirname(opfpath), opf.cover)
if os.access(cpath, os.R_OK): if os.access(cpath, os.R_OK):
fmt = cpath.rpartition('.')[-1] fmt = cpath.rpartition('.')[-1]
data = open(cpath, 'rb').read() data = open(cpath, 'rb').read()
mi.cover_data = (fmt, data) mi.cover_data = (fmt, data)

View File

@ -218,7 +218,7 @@ class Serializer(object):
for elem in item.data.find(XHTML('body')): for elem in item.data.find(XHTML('body')):
self.serialize_elem(elem, item) self.serialize_elem(elem, item)
#buffer.write('</mbp:section>') #buffer.write('</mbp:section>')
buffer.write('</mbp:pagebreak>') buffer.write('<mbp:pagebreak/>')
def serialize_elem(self, elem, item, nsrmap=NSRMAP): def serialize_elem(self, elem, item, nsrmap=NSRMAP):
buffer = self.buffer buffer = self.buffer

View File

@ -69,6 +69,8 @@ def _config():
'clicked')) 'clicked'))
c.add_opt('show_donate_button', default=True, c.add_opt('show_donate_button', default=True,
help='Show donation button') help='Show donation button')
c.add_opt('asked_library_thing_password', default=False,
help='Asked library thing password at least once.')
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()

View File

@ -25,24 +25,48 @@ from calibre import islinux
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.customize.ui import run_plugins_on_import from calibre.customize.ui import run_plugins_on_import
from calibre.gui2 import config as gui_conf
class CoverFetcher(QThread): class CoverFetcher(QThread):
def __init__(self, username, password, isbn, timeout): def __init__(self, username, password, isbn, timeout, title, author):
self.username = username self.username = username.strip() if username else username
self.password = password self.password = password.strip() if password else password
self.timeout = timeout self.timeout = timeout
self.isbn = isbn self.isbn = isbn
self.title = title
self.needs_isbn = False
self.author = author
QThread.__init__(self) QThread.__init__(self)
self.exception = self.traceback = self.cover_data = None self.exception = self.traceback = self.cover_data = None
def run(self): def run(self):
try: try:
login(self.username, self.password, force=False) if not self.isbn:
from calibre.ebooks.metadata.fetch import search
if not self.title:
self.needs_isbn = True
return
au = self.author if self.author else None
key = prefs['isbndb_com_key']
if not key:
key = None
results = search(title=self.title, author=au,
isbndb_key=key)[0]
results = sorted([x.isbn for x in results if x.isbn],
cmp=lambda x,y:cmp(len(x),len(y)), reverse=True)
if not results:
self.needs_isbn = True
return
self.isbn = results[0]
if self.username and self.password:
login(self.username, self.password, force=False)
self.cover_data = cover_from_isbn(self.isbn, timeout=self.timeout)[0] self.cover_data = cover_from_isbn(self.isbn, timeout=self.timeout)[0]
except Exception, e: except Exception, e:
self.exception = e self.exception = e
self.traceback = traceback.format_exc() self.traceback = traceback.format_exc()
print self.traceback
@ -64,6 +88,8 @@ class AuthorCompleter(QCompleter):
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
COVER_FETCH_TIMEOUT = 240 # seconds
def do_reset_cover(self, *args): def do_reset_cover(self, *args):
pix = QPixmap(':/images/book.svg') pix = QPixmap(':/images/book.svg')
self.cover.setPixmap(pix) self.cover.setPixmap(pix)
@ -345,36 +371,39 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def lt_password_dialog(self): def lt_password_dialog(self):
return PasswordDialog(self, 'LibraryThing account', return PasswordDialog(self, 'LibraryThing account',
_('<p>Enter your username and password for <b>LibraryThing.com</b>. <br/>If you do not have one, you can <a href=\'http://www.librarything.com\'>register</a> for free!.</p>')) _('<p>Enter your username and password for '
'<b>LibraryThing.com</b>. This is <b>optional</b>. It will '
'make fetching of covers faster and more reliable.<br/>If '
'you do not have an account, you can '
'<a href=\'http://www.librarything.com\'>register</a> for '
'free.</p>'))
def change_password(self): def change_password(self):
d = self.lt_password_dialog() d = self.lt_password_dialog()
d.exec_() d.exec_()
def fetch_cover(self): def fetch_cover(self):
isbn = qstring_to_unicode(self.isbn.text()) isbn = unicode(self.isbn.text()).strip()
if isbn: d = self.lt_password_dialog()
d = self.lt_password_dialog() if not gui_conf['asked_library_thing_password'] and \
if not d.username() or not d.password(): (not d.username() or not d.password()):
d.exec_() d.exec_()
if d.result() != PasswordDialog.Accepted: gui_conf['asked_library_thing_password'] = True
return self.fetch_cover_button.setEnabled(False)
self.fetch_cover_button.setEnabled(False) self.setCursor(Qt.WaitCursor)
self.setCursor(Qt.WaitCursor) title, author = map(unicode, (self.title.text(), self.authors.text()))
self.cover_fetcher = CoverFetcher(d.username(), d.password(), isbn, self.cover_fetcher = CoverFetcher(d.username(), d.password(), isbn,
self.timeout) self.timeout, title, author)
self.cover_fetcher.start() self.cover_fetcher.start()
self._hangcheck = QTimer(self) self._hangcheck = QTimer(self)
self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck) self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck)
self.cf_start_time = time.time() self.cf_start_time = time.time()
self.pi.start(_('Downloading cover...')) self.pi.start(_('Downloading cover...'))
self._hangcheck.start(100) self._hangcheck.start(100)
else:
error_dialog(self, _('Cannot fetch cover'),
_('You must specify the ISBN identifier for this book.')).exec_()
def hangcheck(self): def hangcheck(self):
if not (self.cover_fetcher.isFinished() or time.time()-self.cf_start_time > 150): if not self.cover_fetcher.isFinished() and \
time.time()-self.cf_start_time < self.COVER_FETCH_TIMEOUT:
return return
self._hangcheck.stop() self._hangcheck.stop()
@ -385,6 +414,11 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
_('<b>Could not fetch cover.</b><br/>')+ _('<b>Could not fetch cover.</b><br/>')+
_('The download timed out.')).exec_() _('The download timed out.')).exec_()
return return
if self.cover_fetcher.needs_isbn:
error_dialog(self, _('Cannot fetch cover'),
_('Could not find cover for this book. Try '
'specifying the ISBN first.')).exec_()
return
if self.cover_fetcher.exception is not None: if self.cover_fetcher.exception is not None:
err = self.cover_fetcher.exception err = self.cover_fetcher.exception
error_dialog(self, _('Cannot fetch cover'), error_dialog(self, _('Cannot fetch cover'),

View File

@ -1,7 +1,8 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class> <class>Dialog</class>
<widget class="QDialog" name="Dialog" > <widget class="QDialog" name="Dialog">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
@ -9,66 +10,70 @@
<height>209</height> <height>209</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Password needed</string> <string>Password needed</string>
</property> </property>
<property name="windowIcon" > <property name="windowIcon">
<iconset resource="../images.qrc" >:/images/mimetypes/unknown.svg</iconset> <iconset resource="../images.qrc">
<normaloff>:/images/mimetypes/unknown.svg</normaloff>:/images/mimetypes/unknown.svg</iconset>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout">
<item row="0" column="1" > <item row="0" column="1">
<widget class="QLabel" name="msg" > <widget class="QLabel" name="msg">
<property name="text" > <property name="text">
<string>TextLabel</string> <string>TextLabel</string>
</property> </property>
<property name="openExternalLinks" > <property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="1" column="0">
<widget class="QLabel" name="label" > <widget class="QLabel" name="label">
<property name="text" > <property name="text">
<string>&amp;Username:</string> <string>&amp;Username:</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>gui_username</cstring> <cstring>gui_username</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="1" column="1">
<widget class="QLineEdit" name="gui_username" /> <widget class="QLineEdit" name="gui_username"/>
</item> </item>
<item row="2" column="0" > <item row="2" column="0">
<widget class="QLabel" name="label_2" > <widget class="QLabel" name="label_2">
<property name="text" > <property name="text">
<string>&amp;Password:</string> <string>&amp;Password:</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>gui_password</cstring> <cstring>gui_password</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="2" column="1">
<widget class="QLineEdit" name="gui_password" > <widget class="QLineEdit" name="gui_password">
<property name="echoMode" > <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::Password</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" > <item row="4" column="1">
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation" > <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons" > <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" > <item row="3" column="1">
<widget class="QCheckBox" name="show_password" > <widget class="QCheckBox" name="show_password">
<property name="text" > <property name="text">
<string>&amp;Show password</string> <string>&amp;Show password</string>
</property> </property>
</widget> </widget>
@ -76,7 +81,7 @@
</layout> </layout>
</widget> </widget>
<resources> <resources>
<include location="../images.qrc" /> <include location="../images.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>
@ -85,11 +90,11 @@
<receiver>Dialog</receiver> <receiver>Dialog</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>248</x> <x>248</x>
<y>254</y> <y>254</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>274</y> <y>274</y>
</hint> </hint>
@ -101,11 +106,11 @@
<receiver>Dialog</receiver> <receiver>Dialog</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>316</x> <x>316</x>
<y>260</y> <y>260</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>274</y> <y>274</y>
</hint> </hint>

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

View File

@ -30,31 +30,31 @@ build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S')
server_resources['jquery.js'] = jquery server_resources['jquery.js'] = jquery
def expose(func): def expose(func):
def do(self, *args, **kwargs): def do(self, *args, **kwargs):
dict.update(cherrypy.response.headers, {'Server':self.server_name}) dict.update(cherrypy.response.headers, {'Server':self.server_name})
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return cherrypy.expose(do) return cherrypy.expose(do)
log_access_file = os.path.join(config_dir, 'server_access_log.txt') log_access_file = os.path.join(config_dir, 'server_access_log.txt')
log_error_file = os.path.join(config_dir, 'server_error_log.txt') log_error_file = os.path.join(config_dir, 'server_error_log.txt')
class LibraryServer(object): class LibraryServer(object):
server_name = __appname__ + '/' + __version__ server_name = __appname__ + '/' + __version__
BOOK = textwrap.dedent('''\ BOOK = textwrap.dedent('''\
<book xmlns:py="http://genshi.edgewall.org/" <book xmlns:py="http://genshi.edgewall.org/"
id="${r[0]}" id="${r[0]}"
title="${r[1]}" title="${r[1]}"
sort="${r[11]}" sort="${r[11]}"
author_sort="${r[12]}" author_sort="${r[12]}"
authors="${authors}" authors="${authors}"
rating="${r[4]}" rating="${r[4]}"
timestamp="${r[5].strftime('%Y/%m/%d %H:%M:%S')}" timestamp="${r[5].strftime('%Y/%m/%d %H:%M:%S')}"
size="${r[6]}" size="${r[6]}"
isbn="${r[14] if r[14] else ''}" isbn="${r[14] if r[14] else ''}"
formats="${r[13] if r[13] else ''}" formats="${r[13] if r[13] else ''}"
series = "${r[9] if r[9] else ''}" series = "${r[9] if r[9] else ''}"
@ -63,7 +63,7 @@ class LibraryServer(object):
publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''} publisher="${r[3] if r[3] else ''}">${r[8] if r[8] else ''}
</book> </book>
''') ''')
LIBRARY = MarkupTemplate(textwrap.dedent('''\ LIBRARY = MarkupTemplate(textwrap.dedent('''\
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}"> <library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}">
@ -72,7 +72,7 @@ class LibraryServer(object):
</py:for> </py:for>
</library> </library>
''')) '''))
STANZA_ENTRY=MarkupTemplate(textwrap.dedent('''\ STANZA_ENTRY=MarkupTemplate(textwrap.dedent('''\
<entry xmlns:py="http://genshi.edgewall.org/"> <entry xmlns:py="http://genshi.edgewall.org/">
<title>${record[FM['title']]}</title> <title>${record[FM['title']]}</title>
@ -87,7 +87,7 @@ class LibraryServer(object):
</content> </content>
</entry> </entry>
''')) '''))
STANZA = MarkupTemplate(textwrap.dedent('''\ STANZA = MarkupTemplate(textwrap.dedent('''\
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:py="http://genshi.edgewall.org/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:py="http://genshi.edgewall.org/">
@ -107,7 +107,7 @@ class LibraryServer(object):
</feed> </feed>
''')) '''))
def __init__(self, db, opts, embedded=False, show_tracebacks=True): def __init__(self, db, opts, embedded=False, show_tracebacks=True):
self.db = db self.db = db
for item in self.db: for item in self.db:
@ -116,7 +116,7 @@ class LibraryServer(object):
self.opts = opts self.opts = opts
self.max_cover_width, self.max_cover_height = \ self.max_cover_width, self.max_cover_height = \
map(int, self.opts.max_cover.split('x')) map(int, self.opts.max_cover.split('x'))
cherrypy.config.update({ cherrypy.config.update({
'log.screen' : opts.develop, 'log.screen' : opts.develop,
'engine.autoreload_on' : opts.develop, 'engine.autoreload_on' : opts.develop,
@ -141,10 +141,10 @@ class LibraryServer(object):
'tools.digest_auth.realm' : (_('Password to access your calibre library. Username is ') + opts.username.strip()).encode('ascii', 'replace'), 'tools.digest_auth.realm' : (_('Password to access your calibre library. Username is ') + opts.username.strip()).encode('ascii', 'replace'),
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()}, 'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
} }
self.is_running = False self.is_running = False
self.exception = None self.exception = None
def setup_loggers(self): def setup_loggers(self):
access_file = log_access_file access_file = log_access_file
error_file = log_error_file error_file = log_error_file
@ -152,20 +152,20 @@ class LibraryServer(object):
maxBytes = getattr(log, "rot_maxBytes", 10000000) maxBytes = getattr(log, "rot_maxBytes", 10000000)
backupCount = getattr(log, "rot_backupCount", 1000) backupCount = getattr(log, "rot_backupCount", 1000)
# Make a new RotatingFileHandler for the error log. # Make a new RotatingFileHandler for the error log.
h = RotatingFileHandler(error_file, 'a', maxBytes, backupCount) h = RotatingFileHandler(error_file, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG) h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt) h.setFormatter(cherrypy._cplogging.logfmt)
log.error_log.addHandler(h) log.error_log.addHandler(h)
# Make a new RotatingFileHandler for the access log. # Make a new RotatingFileHandler for the access log.
h = RotatingFileHandler(access_file, 'a', maxBytes, backupCount) h = RotatingFileHandler(access_file, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG) h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt) h.setFormatter(cherrypy._cplogging.logfmt)
log.access_log.addHandler(h) log.access_log.addHandler(h)
def start(self): def start(self):
self.is_running = False self.is_running = False
self.setup_loggers() self.setup_loggers()
@ -173,7 +173,7 @@ class LibraryServer(object):
try: try:
cherrypy.engine.start() cherrypy.engine.start()
self.is_running = True self.is_running = True
publish_zeroconf('Books in calibre', '_stanza._tcp', publish_zeroconf('Books in calibre', '_stanza._tcp',
self.opts.port, {'path':'/stanza'}) self.opts.port, {'path':'/stanza'})
cherrypy.engine.block() cherrypy.engine.block()
except Exception, e: except Exception, e:
@ -181,10 +181,10 @@ class LibraryServer(object):
finally: finally:
self.is_running = False self.is_running = False
stop_zeroconf() stop_zeroconf()
def exit(self): def exit(self):
cherrypy.engine.exit() cherrypy.engine.exit()
def get_cover(self, id, thumbnail=False): def get_cover(self, id, thumbnail=False):
cover = self.db.cover(id, index_is_id=True, as_file=False) cover = self.db.cover(id, index_is_id=True, as_file=False)
if cover is None: if cover is None:
@ -196,14 +196,14 @@ class LibraryServer(object):
try: try:
if QApplication.instance() is None: if QApplication.instance() is None:
QApplication([]) QApplication([])
im = QImage() im = QImage()
im.loadFromData(cover) im.loadFromData(cover)
if im.isNull(): if im.isNull():
raise cherrypy.HTTPError(404, 'No valid cover found') raise cherrypy.HTTPError(404, 'No valid cover found')
width, height = im.width(), im.height() width, height = im.width(), im.height()
scaled, width, height = fit_image(width, height, scaled, width, height = fit_image(width, height,
60 if thumbnail else self.max_cover_width, 60 if thumbnail else self.max_cover_width,
80 if thumbnail else self.max_cover_height) 80 if thumbnail else self.max_cover_height)
if not scaled: if not scaled:
return cover return cover
@ -217,7 +217,7 @@ class LibraryServer(object):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err) raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err)
def get_format(self, id, format): def get_format(self, id, format):
format = format.upper() format = format.upper()
fmt = self.db.format(id, format, index_is_id=True, as_file=True, mode='rb') fmt = self.db.format(id, format, index_is_id=True, as_file=True, mode='rb')
@ -232,7 +232,7 @@ class LibraryServer(object):
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
return fmt.read() return fmt.read()
def sort(self, items, field, order): def sort(self, items, field, order):
field = field.lower().strip() field = field.lower().strip()
if field == 'author': if field == 'author':
@ -243,10 +243,23 @@ class LibraryServer(object):
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field) raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
cmpf = cmp if field in ('rating', 'size', 'timestamp') else \ cmpf = cmp if field in ('rating', 'size', 'timestamp') else \
lambda x, y: cmp(x.lower() if x else '', y.lower() if y else '') lambda x, y: cmp(x.lower() if x else '', y.lower() if y else '')
field = FIELD_MAP[field] if field == 'series':
getter = operator.itemgetter(field) items.sort(cmp=self.seriescmp, reverse=not order)
items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y)), reverse=not order) else:
field = FIELD_MAP[field]
getter = operator.itemgetter(field)
items.sort(cmp=lambda x, y: cmpf(getter(x), getter(y)), reverse=not order)
def seriescmp(self, x, y):
si = FIELD_MAP['series']
try:
ans = cmp(x[si].lower(), y[si].lower())
except AttributeError: # Some entries may be None
ans = cmp(x[si], y[si])
if ans != 0: return ans
return cmp(x[FIELD_MAP['series_index']], y[FIELD_MAP['series_index']])
def last_modified(self, updated): def last_modified(self, updated):
lm = updated.strftime('day, %d month %Y %H:%M:%S GMT') lm = updated.strftime('day, %d month %Y %H:%M:%S GMT')
day ={0:'Sun', 1:'Mon', 2:'Tue', 3:'Wed', 4:'Thu', 5:'Fri', 6:'Sat'} day ={0:'Sun', 1:'Mon', 2:'Tue', 3:'Wed', 4:'Thu', 5:'Fri', 6:'Sat'}
@ -254,8 +267,8 @@ class LibraryServer(object):
month = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', month = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul',
8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'} 8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
return lm.replace('month', month[updated.month]) return lm.replace('month', month[updated.month])
@expose @expose
def stanza(self): def stanza(self):
' Feeds to read calibre books on a ipod with stanza.' ' Feeds to read calibre books on a ipod with stanza.'
@ -264,7 +277,7 @@ class LibraryServer(object):
r = record[FIELD_MAP['formats']] r = record[FIELD_MAP['formats']]
r = r.upper() if r else '' r = r.upper() if r else ''
if 'EPUB' in r or 'PDB' in r: if 'EPUB' in r or 'PDB' in r:
authors = ' & '.join([i.replace('|', ',') for i in authors = ' & '.join([i.replace('|', ',') for i in
record[FIELD_MAP['authors']].split(',')]) record[FIELD_MAP['authors']].split(',')])
extra = [] extra = []
rating = record[FIELD_MAP['rating']] rating = record[FIELD_MAP['rating']]
@ -276,7 +289,7 @@ class LibraryServer(object):
extra.append('TAGS: %s<br />'%', '.join(tags.split(','))) extra.append('TAGS: %s<br />'%', '.join(tags.split(',')))
series = record[FIELD_MAP['series']] series = record[FIELD_MAP['series']]
if series: if series:
extra.append('SERIES: %s [%d]<br />'%(series, extra.append('SERIES: %s [%d]<br />'%(series,
record[FIELD_MAP['series_index']])) record[FIELD_MAP['series_index']]))
fmt = 'epub' if 'EPUB' in r else 'pdb' fmt = 'epub' if 'EPUB' in r else 'pdb'
mimetype = guess_type('dummy.'+fmt)[0] mimetype = guess_type('dummy.'+fmt)[0]
@ -288,24 +301,24 @@ class LibraryServer(object):
mimetype=mimetype, mimetype=mimetype,
fmt=fmt, fmt=fmt,
).render('xml').decode('utf8')) ).render('xml').decode('utf8'))
updated = self.db.last_modified() updated = self.db.last_modified()
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Content-Type'] = 'text/xml'
return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP, return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP,
updated=updated, id='urn:calibre:main').render('xml') updated=updated, id='urn:calibre:main').render('xml')
@expose @expose
def library(self, start='0', num='50', sort=None, search=None, def library(self, start='0', num='50', sort=None, search=None,
_=None, order='ascending'): _=None, order='ascending'):
''' '''
Serves metadata from the calibre database as XML. Serves metadata from the calibre database as XML.
:param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`.
:param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax
:param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results
:param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching
''' '''
try: try:
start = int(start) start = int(start)
@ -321,19 +334,19 @@ class LibraryServer(object):
items = [r for r in iter(self.db) if r[0] in ids] items = [r for r in iter(self.db) if r[0] in ids]
if sort is not None: if sort is not None:
self.sort(items, sort, order) self.sort(items, sort, order)
book, books = MarkupTemplate(self.BOOK), [] book, books = MarkupTemplate(self.BOOK), []
for record in items[start:start+num]: for record in items[start:start+num]:
aus = record[2] if record[2] else _('Unknown') aus = record[2] if record[2] else _('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8'))
updated = self.db.last_modified() updated = self.db.last_modified()
cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Content-Type'] = 'text/xml'
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
return self.LIBRARY.generate(books=books, start=start, updated=updated, return self.LIBRARY.generate(books=books, start=start, updated=updated,
total=len(ids)).render('xml') total=len(ids)).render('xml')
@expose @expose
def index(self, **kwargs): def index(self, **kwargs):
'The / URL' 'The / URL'
@ -341,8 +354,8 @@ class LibraryServer(object):
if stanza == 919: if stanza == 919:
return self.static('index.html') return self.static('index.html')
return self.stanza() return self.stanza()
@expose @expose
def get(self, what, id): def get(self, what, id):
'Serves files, covers, thumbnails from the calibre database' 'Serves files, covers, thumbnails from the calibre database'
@ -361,7 +374,7 @@ class LibraryServer(object):
if what == 'cover': if what == 'cover':
return self.get_cover(id) return self.get_cover(id)
return self.get_format(id, what) return self.get_format(id, what)
@expose @expose
def static(self, name): def static(self, name):
'Serves static content' 'Serves static content'
@ -392,11 +405,11 @@ def start_threaded_server(db, opts):
server.thread.setDaemon(True) server.thread.setDaemon(True)
server.thread.start() server.thread.start()
return server return server
def stop_threaded_server(server): def stop_threaded_server(server):
server.exit() server.exit()
server.thread = None server.thread = None
def option_parser(): def option_parser():
return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.')) return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))

View File

@ -39,7 +39,7 @@ recipe_modules = ['recipe_' + r for r in (
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs', 'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet', 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet',
'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en', 'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en',
'moneynews', 'moneynews', 'der_standard', 'diepresse', 'nzz_ger', 'hna',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -0,0 +1,42 @@
''' http://www.derstandard.at - Austrian Newspaper '''
import re
from calibre.web.feeds.news import BasicNewsRecipe
class DerStandardRecipe(BasicNewsRecipe):
title = u'derStandard'
__author__ = 'Gerhard Aigner'
oldest_article = 1
max_articles_per_feed = 100
feeds = [(u'International', u'http://derstandard.at/?page=rss&ressort=internationalpolitik'),
(u'Inland', u'http://derstandard.at/?page=rss&ressort=innenpolitik'),
(u'Wirtschaft', u'http://derstandard.at/?page=rss&ressort=investor'),
(u'Web', u'http://derstandard.at/?page=rss&ressort=webstandard'),
(u'Sport', u'http://derstandard.at/?page=rss&ressort=sport'),
(u'Panorama', u'http://derstandard.at/?page=rss&ressort=panorama'),
(u'Etat', u'http://derstandard.at/?page=rss&ressort=etat'),
(u'Kultur', u'http://derstandard.at/?page=rss&ressort=kultur'),
(u'Wissenschaft', u'http://derstandard.at/?page=rss&ressort=wissenschaft'),
(u'Gesundheit', u'http://derstandard.at/?page=rss&ressort=gesundheit'),
(u'Bildung', u'http://derstandard.at/?page=rss&ressort=subildung')]
encoding = 'utf-8'
language = _('German')
recursions = 0
remove_tags = [dict(name='div'), dict(name='a'), dict(name='link'), dict(name='meta'),
dict(name='form',attrs={'name':'sitesearch'}), dict(name='hr')]
preprocess_regexps = [
(re.compile(r'\[[\d*]\]', re.DOTALL|re.IGNORECASE), lambda match: ''),
(re.compile(r'bgcolor="#\w{3,6}"', re.DOTALL|re.IGNORECASE), lambda match: '')
]
def print_version(self, url):
return url.replace('?id=', 'txt/?id=')
def get_article_url(self, article):
'''if the article links to a index page (ressort) or a picture gallery
(ansichtssache), don't add it'''
if (article.link.count('ressort') > 0 or article.title.lower().count('ansichtssache') > 0):
return None
return article.link

View File

@ -0,0 +1,40 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class DiePresseRecipe(BasicNewsRecipe):
title = u'diePresse'
oldest_article = 1
max_articles_per_feed = 100
recursions = 0
language = _('German')
__author__ = 'Gerhard Aigner'
preprocess_regexps = [
(re.compile(r'Textversion', re.DOTALL), lambda match: ''),
]
remove_tags = [dict(name='hr'),
dict(name='br'),
dict(name='small'),
dict(name='img'),
dict(name='div', attrs={'class':'textnavi'}),
dict(name='h1', attrs={'class':'titel'}),
dict(name='a', attrs={'class':'print'}),
dict(name='div', attrs={'class':'hline'})]
feeds = [(u'Politik', u'http://diepresse.com/rss/Politik'),
(u'Wirtschaft', u'http://diepresse.com/rss/Wirtschaft'),
(u'Europa', u'http://diepresse.com/rss/EU'),
(u'Panorama', u'http://diepresse.com/rss/Panorama'),
(u'Sport', u'http://diepresse.com/rss/Sport'),
(u'Kultur', u'http://diepresse.com/rss/Kultur'),
(u'Leben', u'http://diepresse.com/rss/Leben'),
(u'Tech', u'http://diepresse.com/rss/Tech'),
(u'Science', u'http://diepresse.com/rss/Science'),
(u'Bildung', u'http://diepresse.com/rss/Bildung'),
(u'Gesundheit', u'http://diepresse.com/rss/Gesundheit'),
(u'Recht', u'http://diepresse.com/rss/Recht'),
(u'Spectrum', u'http://diepresse.com/rss/Spectrum'),
(u'Meinung', u'http://diepresse.com/rss/Meinung')]
def print_version(self, url):
return url.replace('home','text/home')

View File

@ -0,0 +1,40 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Fetch Hessisch Niedersachsische Allgemeine.
'''
from calibre.web.feeds.news import BasicNewsRecipe
class hnaDe(BasicNewsRecipe):
title = 'HNA'
description = 'local news from Hessen/Germany'
__author__ = 'Oliver Niesner'
use_embedded_content = False
language = _('German')
use_embedded_content = False
timefmt = ' [%d %b %Y]'
max_articles_per_feed = 40
no_stylesheets = True
encoding = 'iso-8859-1'
remove_tags = [dict(id='topnav'),
dict(id='nav_main'),
dict(id='suchen'),
dict(id=''),
dict(name='span'),
dict(name='ul', attrs={'class':'linklist'}),
dict(name='a', attrs={'href':'#'}),
dict(name='p', attrs={'class':'breadcrumb'}),
dict(name='p', attrs={'class':'h5'})]
#remove_tags_after = [dict(name='div', attrs={'class':'rahmenbreaking'})]
remove_tags_after = [dict(name='a', attrs={'href':'#'})]
feeds = [ ('hna_soehre', 'http://feeds2.feedburner.com/hna/soehre'),
('hna_kassel', 'http://feeds2.feedburner.com/hna/kassel') ]

View File

@ -1,80 +1,78 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' '''
Fetch Linuxdevices. Fetch Linuxdevices.
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Sueddeutsche(BasicNewsRecipe): class Sueddeutsche(BasicNewsRecipe):
title = u'Linuxdevices' title = u'Linuxdevices'
description = 'News about Linux driven Hardware' description = 'News about Linux driven Hardware'
__author__ = 'Oliver Niesner' __author__ = 'Oliver Niesner'
use_embedded_content = False use_embedded_content = False
timefmt = ' [%a, %d %b %Y]' timefmt = ' [%a %d %b %Y]'
language = _('English') max_articles_per_feed = 50
max_articles_per_feed = 50 no_stylesheets = True
no_stylesheets = True html2epub_options = 'linearize_tables = True\nbase_font_size2=14'
encoding = 'latin1' encoding = 'latin1'
remove_tags_after = [dict(id='nointelliTXT')]
filter_regexps = [r'ad\.doubleclick\.net'] remove_tags_after = [dict(id='nointelliTXT')]
filter_regexps = [r'ad\.doubleclick\.net']
remove_tags = [dict(name='div', attrs={'class':'bannerSuperBanner'}), remove_tags = [dict(name='div', attrs={'class':'bannerSuperBanner'}),
dict(name='div', attrs={'class':'bannerSky'}), dict(name='div', attrs={'class':'bannerSky'}),
dict(name='div', attrs={'class':'footerLinks'}), dict(name='div', attrs={'class':'footerLinks'}),
dict(name='div', attrs={'class':'seitenanfang'}), dict(name='div', attrs={'class':'seitenanfang'}),
dict(name='td', attrs={'class':'mar5'}), dict(name='td', attrs={'class':'mar5'}),
dict(name='td', attrs={'class':'mar5'}), dict(name='td', attrs={'class':'mar5'}),
dict(name='table', attrs={'class':'pageAktiv'}), dict(name='table', attrs={'class':'pageAktiv'}),
dict(name='table', attrs={'class':'xartable'}), dict(name='table', attrs={'class':'xartable'}),
dict(name='table', attrs={'class':'wpnavi'}), dict(name='table', attrs={'class':'wpnavi'}),
dict(name='table', attrs={'class':'bgcontent absatz'}), dict(name='table', attrs={'class':'bgcontent absatz'}),
dict(name='table', attrs={'class':'footer'}), dict(name='table', attrs={'class':'footer'}),
dict(name='table', attrs={'class':'artikelBox'}), dict(name='table', attrs={'class':'artikelBox'}),
dict(name='table', attrs={'class':'kommentare'}), dict(name='table', attrs={'class':'kommentare'}),
dict(name='table', attrs={'class':'pageBoxBot'}), dict(name='table', attrs={'class':'pageBoxBot'}),
#dict(name='table', attrs={'with':'100%'}), dict(name='td', attrs={'nowrap':'nowrap'}),
dict(name='td', attrs={'nowrap':'nowrap'}), dict(name='td', attrs={'valign':'middle'}),
dict(name='td', attrs={'valign':'middle'}), dict(name='td', attrs={'align':'left'}),
dict(name='td', attrs={'align':'left'}), dict(name='td', attrs={'align':'center'}),
dict(name='td', attrs={'align':'center'}), dict(name='td', attrs={'height':'5'}),
dict(name='td', attrs={'height':'5'}), dict(name='div', attrs={'class':'artikelBox navigatorBox'}),
dict(name='div', attrs={'class':'artikelBox navigatorBox'}), dict(name='div', attrs={'class':'similar-article-box'}),
dict(name='div', attrs={'class':'similar-article-box'}), dict(name='div', attrs={'class':'videoBigHack'}),
dict(name='div', attrs={'class':'videoBigHack'}), dict(name='td', attrs={'class':'artikelDruckenRight'}),
dict(name='td', attrs={'class':'artikelDruckenRight'}), dict(name='td', attrs={'class':'width="200"'}),
dict(name='td', attrs={'class':'width="200"'}), dict(name='a', attrs={'href':'/news'}),
dict(name='a', attrs={'href':'/news'}), dict(name='a', attrs={'href':'/'}),
dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/articles'}),
dict(name='a', attrs={'href':'/articles'}), dict(name='a', attrs={'href':'/cgi-bin/survey/survey.cgi'}),
dict(name='a', attrs={'href':'/cgi-bin/survey/survey.cgi'}), dict(name='a', attrs={'href':'/cgi-bin/board/UltraBoard.pl'}),
dict(name='a', attrs={'href':'/cgi-bin/board/UltraBoard.pl'}), dict(name='iframe'),
dict(name='iframe'), dict(name='form'),
dict(name='form'), dict(name='span', attrs={'class':'hidePrint'}),
#dict(name='tr', attrs={'td':'Click here to learn'}), dict(id='headerLBox'),
dict(name='span', attrs={'class':'hidePrint'}), dict(id='nointelliTXT'),
dict(id='headerLBox'), dict(id='rechteSpalte'),
dict(id='nointelliTXT'), dict(id='newsticker-list-small'),
dict(id='rechteSpalte'), dict(id='ntop5'),
dict(id='newsticker-list-small'), dict(id='ntop5send'),
dict(id='ntop5'), dict(id='ntop5commented'),
dict(id='ntop5send'), dict(id='nnav-bgheader'),
dict(id='ntop5commented'), dict(id='nnav-headerteaser'),
dict(id='nnav-bgheader'), dict(id='nnav-head'),
dict(id='nnav-headerteaser'), dict(id='nnav-top'),
dict(id='nnav-head'), dict(id='nnav-logodiv'),
dict(id='nnav-top'), dict(id='nnav-logo'),
dict(id='nnav-logodiv'), dict(id='nnav-oly'),
dict(id='nnav-logo'), dict(id='readcomment')]
dict(id='nnav-oly'),
dict(id='readcomment')]
feeds = [ (u'Linuxdevices', u'http://www.linuxdevices.com/backend/headlines.rss') ]
feeds = [ (u'Linuxdevices', u'http://www.linuxdevices.com/backend/headlines.rss') ]

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.nzz.ch
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzz(BasicNewsRecipe):
title = 'NZZ Online'
__author__ = 'Darko Miletic'
description = 'Laufend aktualisierte Nachrichten, Analysen und Hintergruende zu Politik, Wirtschaft, Kultur und Sport'
publisher = 'NZZ AG'
category = 'news, politics, nachrichten, Switzerland'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
encoding = 'utf-8'
use_embedded_content = False
lang = 'de-CH'
language = _('German')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em} img {margin-top: 0em; margin-bottom: 0.4em}"'
keep_only_tags = [dict(name='div', attrs={'class':'article'})]
remove_tags = [
dict(name=['object','link','base','script'])
,dict(name='div',attrs={'class':['more','teaser','advXertXoriXals','legal']})
,dict(name='div',attrs={'id':['popup-src','readercomments','google-ad','advXertXoriXals']})
]
feeds = [
(u'Neuste Artikel', u'http://www.nzz.ch/feeds/recent/' )
,(u'International' , u'http://www.nzz.ch/nachrichten/international?rss=true')
,(u'Schweiz' , u'http://www.nzz.ch/nachrichten/schweiz?rss=true')
,(u'Wirtschaft' , u'http://www.nzz.ch/nachrichten/wirtschaft/aktuell?rss=true')
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/nachrichten?rss=true')
,(u'Zuerich' , u'http://www.nzz.ch/nachrichten/zuerich?rss=true')
,(u'Sport' , u'http://www.nzz.ch/nachrichten/sport?rss=true')
,(u'Panorama' , u'http://www.nzz.ch/nachrichten/panorama?rss=true')
,(u'Kultur' , u'http://www.nzz.ch/nachrichten/kultur/aktuell?rss=true')
,(u'Wissenschaft' , u'http://www.nzz.ch/nachrichten/wissenschaft?rss=true')
,(u'Medien' , u'http://www.nzz.ch/nachrichten/medien?rss=true')
,(u'Reisen' , u'http://www.nzz.ch/magazin/reisen?rss=true')
]
def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang
soup.html['lang'] = self.lang
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">'
soup.head.insert(0,mtag)
return soup
def print_version(self, url):
return url + '?printview=true'

View File

@ -8,26 +8,19 @@ Fetch tomshardware.
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class TomsHardwareDe(BasicNewsRecipe): class cdnet(BasicNewsRecipe):
title = 'Tom\'s Hardware German' title = 'tomshardware'
description = 'Computer news in german' description = 'computer news in german'
__author__ = 'Oliver Niesner' __author__ = 'Oliver Niesner'
use_embedded_content = False use_embedded_content = False
timefmt = ' [%d %b %Y]' timefmt = ' [%d %b %Y]'
max_articles_per_feed = 50 max_articles_per_feed = 50
language = _('German')
no_stylesheets = True no_stylesheets = True
language = _('German')
encoding = 'utf-8' encoding = 'utf-8'
#preprocess_regexps = \
# [(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
# [
# (r'<84>', lambda match: ''),
# (r'<93>', lambda match: ''),
# ]
# ]
remove_tags = [dict(id='outside-advert'), remove_tags = [dict(id='outside-advert'),
dict(id='advertRightWhite'), dict(id='advertRightWhite'),
dict(id='header-advert'), dict(id='header-advert'),
@ -36,9 +29,15 @@ class TomsHardwareDe(BasicNewsRecipe):
dict(id='header-top'), dict(id='header-top'),
dict(id='header-tools'), dict(id='header-tools'),
dict(id='nbComment'), dict(id='nbComment'),
dict(id='commentTools'),
dict(id='internalSidebar'), dict(id='internalSidebar'),
dict(id='header-news-infos'), dict(id='header-news-infos'),
dict(id='header-news-tools'),
dict(id='breadcrumbs'), dict(id='breadcrumbs'),
dict(id='emailTools'),
dict(id='bookmarkTools'),
dict(id='printTools'),
dict(id='header-nextNews'),
dict(id=''), dict(id=''),
dict(name='div', attrs={'class':'pyjama'}), dict(name='div', attrs={'class':'pyjama'}),
dict(name='href', attrs={'class':'comment'}), dict(name='href', attrs={'class':'comment'}),
@ -47,8 +46,10 @@ class TomsHardwareDe(BasicNewsRecipe):
dict(name='div', attrs={'class':'greyBox clearfix'}), dict(name='div', attrs={'class':'greyBox clearfix'}),
dict(id='')] dict(id='')]
#remove_tags_before = [dict(id='header-news-title')] #remove_tags_before = [dict(id='header-news-title')]
remove_tags_after = [dict(name='div', attrs={'class':'news-elm'})] remove_tags_after = [dict(name='div', attrs={'class':'btmGreyTables'})]
#remove_tags_after = [dict(name='div', attrs={'class':'intelliTXT'})] #remove_tags_after = [dict(name='div', attrs={'class':'intelliTXT'})]
feeds = [ ('tomshardware', 'http://www.tomshardware.com/de/feeds/rss2/tom-s-hardware-de,12-1.xml') ] feeds = [ ('tomshardware', 'http://www.tomshardware.com/de/feeds/rss2/tom-s-hardware-de,12-1.xml') ]

View File

@ -530,6 +530,7 @@ class build_windows(VMInstaller):
self.run_windows_install_jammer(installer) self.run_windows_install_jammer(installer)
return os.path.basename(installer) return os.path.basename(installer)
@classmethod
def run_windows_install_jammer(self, installer): def run_windows_install_jammer(self, installer):
ibp = os.path.abspath('installer/windows') ibp = os.path.abspath('installer/windows')
sys.path.insert(0, ibp) sys.path.insert(0, ibp)