Pull from trunk

This commit is contained in:
Kovid Goyal 2009-03-14 16:40:59 -07:00
commit 0d878e08ba
45 changed files with 9491 additions and 6687 deletions

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.5.0'
__version__ = '0.5.1'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
'''
Various run time constants.

View File

@ -97,6 +97,8 @@ def xml_to_unicode(raw, verbose=False, strip_encoding_pats=False,
if encoding is None:
encoding = force_encoding(raw, verbose)
try:
if encoding.lower().strip() == 'macintosh':
encoding = 'mac-roman'
raw = raw.decode(encoding, 'replace')
except LookupError:
encoding = 'utf-8'

View File

@ -31,7 +31,7 @@ from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
from calibre.ebooks.oeb.transforms.manglecase import CaseMangler
from calibre.ebooks.mobi.palmdoc import compress_doc
from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
from calibre.ebooks.mobi.mobiml import MBP_NS, MobiMLizer
from calibre.customize.ui import run_plugins_on_postprocess
from calibre.utils.config import Config, StringConfig
@ -160,7 +160,7 @@ class Serializer(object):
hrefs = self.oeb.manifest.hrefs
buffer.write('<guide>')
for ref in self.oeb.guide.values():
path, frag = urldefrag(ref.href)
path = urldefrag(ref.href)[0]
if hrefs[path].media_type not in OEB_DOCS:
continue
buffer.write('<reference type="')
@ -455,8 +455,6 @@ class MobiWriter(object):
self._oeb.logger.info('Serializing images...')
images = [(index, href) for href, index in self._images.items()]
images.sort()
metadata = self._oeb.metadata
coverid = metadata.cover[0] if metadata.cover else None
for _, href in images:
item = self._oeb.manifest.hrefs[href]
try:

View File

@ -159,7 +159,7 @@ class Stylizer(object):
for _, _, cssdict, text, _ in rules:
try:
selector = CSSSelector(text)
except ExpressionError:
except (AssertionError, ExpressionError, etree.XPathSyntaxError):
continue
for elem in selector(tree):
self.style(elem)._update_cssdict(cssdict)

View File

@ -63,6 +63,8 @@ class HTMLTOCAdder(object):
def __call__(self, oeb, context):
if 'toc' in oeb.guide:
return
if not getattr(getattr(oeb, 'toc', False), 'nodes', False):
return
oeb.logger.info('Generating in-line TOC...')
title = self.title or oeb.translate(DEFAULT_TITLE)
style = self.style

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

View File

@ -94,6 +94,7 @@ class DateDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM/dd/yyyy')
qde.setMinimumDate(QDate(100,1,1))
return qde
class BooksModel(QAbstractTableModel):

View File

@ -238,6 +238,7 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.config_button, SIGNAL('clicked(bool)'), self.do_config)
self.connect(self.preferences_action, SIGNAL('triggered(bool)'), self.do_config)
self.connect(self.action_preferences, SIGNAL('triggered(bool)'), self.do_config)
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search)
####################### Library view ########################
@ -1220,12 +1221,14 @@ class Main(MainWindow, Ui_MainWindow):
if ext in config['internally_viewed_formats']:
if ext == 'LRF':
args = ['lrfviewer', name]
self.job_manager.server.run_free_job('lrfviewer', kwdargs=dict(args=args))
self.job_manager.server.run_free_job('lrfviewer',
kwdargs=dict(args=args))
else:
args = ['ebook-viewer', name]
if isosx:
args.append('--raise-window')
self.job_manager.server.run_free_job('ebook-viewer', kwdargs=dict(args=args))
self.job_manager.server.run_free_job('ebook-viewer',
kwdargs=dict(args=args))
else:
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
time.sleep(5) # User feedback
@ -1320,7 +1323,7 @@ class Main(MainWindow, Ui_MainWindow):
############################### Do config ##################################
def do_config(self):
def do_config(self, *args):
if self.job_manager.has_jobs():
d = error_dialog(self, _('Cannot configure'), _('Cannot configure while there are running jobs.'))
d.exec_()

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Kovid Goyal</author>
<class>MainWindow</class>
@ -6,12 +7,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>865</width>
<width>1012</width>
<height>822</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -33,7 +34,7 @@
<item>
<widget class="LocationView" name="location_view">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -110,7 +111,7 @@
<item>
<widget class="QLabel" name="vanity">
<property name="sizePolicy">
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -195,7 +196,7 @@
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -204,10 +205,10 @@
<bool>false</bool>
</property>
<property name="toolTip">
<string>Search the list of books by title or author&lt;br>&lt;br>Words separated by spaces are ANDed</string>
<string>Search the list of books by title or author&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>
<property name="whatsThis">
<string>Search the list of books by title, author, publisher, tags and comments&lt;br>&lt;br>Words separated by spaces are ANDed</string>
<string>Search the list of books by title, author, publisher, tags and comments&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
@ -273,7 +274,7 @@
<item row="2" column="0">
<widget class="QStackedWidget" name="stack">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>100</horstretch>
<verstretch>100</verstretch>
</sizepolicy>
@ -335,7 +336,7 @@
<item>
<widget class="BooksView" name="library_view">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>100</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
@ -375,7 +376,7 @@
<item row="0" column="0">
<widget class="DeviceBooksView" name="memory_view">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>100</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
@ -413,7 +414,7 @@
<item row="0" column="0">
<widget class="DeviceBooksView" name="card_view">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Preferred" >
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>10</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
@ -482,15 +483,16 @@
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_del" />
<addaction name="action_edit"/>
<addaction name="action_convert"/>
<addaction name="action_view"/>
<addaction name="action_news"/>
<addaction name="separator"/>
<addaction name="action_sync"/>
<addaction name="action_save"/>
<addaction name="action_del"/>
<addaction name="separator"/>
<addaction name="action_news" />
<addaction name="action_convert" />
<addaction name="action_view" />
<addaction name="action_preferences"/>
</widget>
<widget class="QStatusBar" name="statusBar">
<property name="mouseTracking">
@ -665,6 +667,21 @@
<string>Send specific format to device</string>
</property>
</action>
<action name="action_preferences">
<property name="icon">
<iconset resource="images.qrc">
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
</property>
<property name="text">
<string>Preferences</string>
</property>
<property name="toolTip">
<string>Configure calibre</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -605,7 +605,7 @@ def config(defaults=None):
else:
c = StringConfig(defaults, desc)
c.add_opt('--raise-window', default=False,
c.add_opt('raise_window', ['--raise-window'], default=False,
help=_('If specified, viewer window will try to come to the '
'front when started.'))
return c

View File

@ -244,7 +244,10 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
if os.path.isdir(path):
dirs.append(path)
else:
if os.path.exists(path):
files.append(path)
else:
print path, 'not found'
formats, metadata = [], []
for book in files:
@ -262,12 +265,14 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
formats.append(format)
metadata.append(mi)
file_duplicates = db.add_books(files, formats, metadata, add_duplicates=add_duplicates)
if not file_duplicates[0]:
file_duplicates = []
else:
if files:
file_duplicates = db.add_books(files, formats, metadata,
add_duplicates=add_duplicates)
if file_duplicates:
file_duplicates = file_duplicates[0]
dir_dups = []
for dir in dirs:
if recurse:
@ -286,7 +291,9 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
db.import_book(mi, formats)
else:
if dir_dups or file_duplicates:
print >>sys.stderr, _('The following books were not added as they already exist in the database (see --duplicates option):')
print >>sys.stderr, _('The following books were not added as '
'they already exist in the database '
'(see --duplicates option):')
for mi, formats in dir_dups:
title = mi.title
if isinstance(title, unicode):

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
HTTP server for remote access to the calibre database.
'''
import sys, textwrap, operator, os, re, logging
import sys, textwrap, operator, os, re, logging, subprocess
from itertools import repeat
from logging.handlers import RotatingFileHandler
from datetime import datetime
@ -23,6 +23,8 @@ from calibre.resources import jquery, server_resources, build_time
from calibre.library import server_config as config
from calibre.library.database2 import LibraryDatabase2, FIELD_MAP
from calibre.utils.config import config_dir
from calibre.utils.mdns import publish as publish_zeroconf, \
stop_server as stop_zeroconf
build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S')
server_resources['jquery.js'] = jquery
@ -171,11 +173,14 @@ class LibraryServer(object):
try:
cherrypy.engine.start()
self.is_running = True
publish_zeroconf('Books in calibre', '_stanza._tcp',
self.opts.port, {'path':'/stanza'})
cherrypy.engine.block()
except Exception, e:
self.exception = e
finally:
self.is_running = False
stop_zeroconf()
def exit(self):
cherrypy.engine.exit()
@ -332,7 +337,11 @@ class LibraryServer(object):
@expose
def index(self, **kwargs):
'The / URL'
stanza = cherrypy.request.headers.get('Stanza-Device-Name', 919)
if stanza == 919:
return self.static('index.html')
return self.stanza()
@expose
def get(self, what, id):

View File

@ -123,14 +123,16 @@ turned into a collection on the reader. Note that the PRS-500 does not support c
How do I use |app| with my iPhone?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
First install the Stanza reader on your iPhone from http://www.lexcycle.com . Then,
* Set the output format for calibre to EPUB (this can be done in the configuration dialog accessed by the little hammer icon next to the search bar)
* Set the output format for calibre to EPUB (The output format can be set next to the big red heart)
* Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button.
* Turn on the Content Server in the configurations dialog and leave |app| running.
* In the Stanza reader on your iPhone, add a new catalog. The URL of the catalog is of the form
* Turn on the Content Server in |app|'s preferences and leave |app| running.
* Now you should be able to access your books on your iPhone. If not, try the following:
In the Stanza reader on your iPhone, add a new catalog. The URL of the catalog is of the form
``http://10.34.56.89:8080/stanza``, where you should replace the IP address ``10.34.56.89``
with the IP address of your computer. Stanza will the use the |app| content server to access all the
EPUB books in your |app| database.
Library Management
------------------

View File

@ -227,7 +227,8 @@ class WorkerMother(object):
return env
def spawn_free_spirit_osx(self, arg, type='free_spirit'):
script = 'from calibre.parallel import main; main(args=["calibre-parallel", %s]);'%repr(arg)
script = ('from calibre.parallel import main; '
'main(args=["calibre-parallel", %s]);')%repr(arg)
exe = self.gui_executable if type == 'free_spirit' else self.executable
cmdline = [exe, '-c', self.prefix+script]
child = WorkerStatus(subprocess.Popen(cmdline, env=self.get_env()))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1574
src/calibre/utils/Zeroconf.py Executable file

File diff suppressed because it is too large Load Diff

97
src/calibre/utils/mdns.py Normal file
View File

@ -0,0 +1,97 @@
from __future__ import with_statement
__license__ = 'GPL 3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import socket
_server = None
def get_external_ip():
'Get IP address of interface used to connect to the outside world'
try:
ipaddr = socket.gethostbyname(socket.gethostname())
except:
ipaddr = '127.0.0.1'
if ipaddr == '127.0.0.1':
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('google.com', 0))
ipaddr = s.getsockname()[0]
except:
pass
return ipaddr
def start_server():
global _server
if _server is None:
from calibre.utils.Zeroconf import Zeroconf
_server = Zeroconf()
return _server
def publish(desc, type, port, properties=None, add_hostname=True):
'''
Publish a service.
:param desc: Description of service
:param type: Name and type of service. For example _stanza._tcp
:param port: Port the service listens on
:param properties: An optional dictionary whose keys and values will be put
into the TXT record.
'''
port = int(port)
server = start_server()
hostname = socket.gethostname()
if add_hostname:
desc += ' (on %s)'%hostname
local_ip = get_external_ip()
type = type+'.local.'
from calibre.utils.Zeroconf import ServiceInfo
service = ServiceInfo(type, desc+'.'+type,
address=socket.inet_aton(local_ip),
port=port,
properties=properties,
server=hostname+'.local.')
server.registerService(service)
def stop_server():
global _server
if _server is not None:
_server.close()
'''
class Publish(object):
def __init__(self, desc, name, port, txt=''):
self.desc = desc
self.name = name
self.port = port
self.txt = txt
def start(self):
if iswindows:
return
if isosx:
args = ['dns-sd', self.desc, self.name, '.', self.port]
else:
args = ['avahi-publish-service', self.desc, self.name, self.port]
if self.txt:
args.append(self.txt)
self.process = subprocess.Popen(args)
def stop(self):
if iswindows:
pass
else:
process = getattr(self, 'process', None)
if process is not None:
process.poll()
if process.returncode is not None:
process.terminate()
process.poll()
if process.returncode is not None:
process.kill()
def publish(desc, name, port, txt):
'''

View File

@ -34,6 +34,7 @@ recipe_modules = ['recipe_' + r for r in (
'al_jazeera', 'winsupersite', 'borba', 'courrierinternational',
'lamujerdemivida', 'soldiers', 'theonion', 'news_times',
'el_universal', 'mediapart', 'wikinews_en', 'ecogeek', 'daily_mail',
'new_york_review_of_books_no_sub', 'politico',
)]
import re, imp, inspect, time, os

View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
nybooks.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
from lxml import html
from calibre.constants import preferred_encoding
class NewYorkReviewOfBooks(BasicNewsRecipe):
title = u'New York Review of Books (no subscription)'
description = u'Book reviews'
language = _('English')
__author__ = 'Kovid Goyal'
remove_tags_before = {'id':'container'}
remove_tags = [{'class':['noprint', 'ad', 'footer']}, {'id':'right-content'}]
def parse_index(self):
root = html.fromstring(self.browser.open('http://www.nybooks.com/current-issue').read())
date = root.xpath('//h4[@class = "date"]')[0]
self.timefmt = ' ['+date.text.encode(preferred_encoding)+']'
articles = []
for tag in date.itersiblings():
if tag.tag == 'h4': break
if tag.tag == 'p':
if tag.get('class') == 'indented':
articles[-1]['description'] += html.tostring(tag)
else:
href = tag.xpath('descendant::a[@href]')[0].get('href')
article = {
'title': u''.join(tag.xpath('descendant::text()')),
'date' : '',
'url' : 'http://www.nybooks.com'+href,
'description': '',
}
articles.append(article)
return [('Current Issue', articles)]

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
politico.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Politico(BasicNewsRecipe):
title = 'Politico'
__author__ = 'Darko Miletic'
description = 'Political news from USA'
publisher = 'Capitol News Company, LLC'
category = 'news, politics, USA'
oldest_article = 7
max_articles_per_feed = 100
use_embedded_content = False
no_stylesheets = True
remove_javascript = True
encoding = 'cp1252'
language = _('English')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
, '--ignore-tables'
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
remove_tags = [dict(name=['notags','embed','object','link','img'])]
feeds = [
(u'Top Stories' , u'http://www.politico.com/rss/politicopicks.xml' )
,(u'Congress' , u'http://www.politico.com/rss/congress.xml' )
,(u'Ideas' , u'http://www.politico.com/rss/ideas.xml' )
,(u'Life' , u'http://www.politico.com/rss/life.xml' )
,(u'Lobbyists' , u'http://www.politico.com/rss/lobbyists.xml' )
,(u'Pitboss' , u'http://www.politico.com/rss/pitboss.xml' )
,(u'Politics' , u'http://www.politico.com/rss/politics.xml' )
,(u'Roger Simon' , u'http://www.politico.com/rss/rogersimon.xml' )
,(u'Suite Talk' , u'http://www.politico.com/rss/suitetalk.xml' )
,(u'Playbook' , u'http://www.politico.com/rss/playbook.xml' )
,(u'The Huddle' , u'http://www.politico.com/rss/huddle.xml' )
]
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="en-US"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
return soup
def print_url(self, soup, default):
printtags = soup.findAll('a',href=True)
for printtag in printtags:
if printtag.string == "Print":
return printtag['href']
return default
def print_version(self, url):
soup = self.index_to_soup(url)
return self.print_url(soup, None)

View File

@ -14,6 +14,7 @@ class Time(BasicNewsRecipe):
description = 'Weekly magazine'
oldest_article = 7
max_articles_per_feed = 100
encoding = 'utf-8'
no_stylesheets = True
language = _('English')
use_embedded_content = False

View File

@ -5,3 +5,4 @@
* Use multiprocessing for cpu_count instead of QThread
* Rationalize books table. Add a pubdate column, remove the uri column (and associated support in add_books) and convert series_index to a float.

View File

@ -709,6 +709,7 @@ class upload(OptionlessCommand):
('stage3', None)
]
try:
class upload_rss(OptionlessCommand):
from bzrlib import log as blog
@ -769,5 +770,6 @@ class upload_rss(OptionlessCommand):
log.show_log(b, lf)
lf.rss.write_xml(open('/tmp/releases.xml', 'wb'))
subprocess.check_call('scp /tmp/releases.xml divok:/var/www/calibre.kovidgoyal.net/htdocs/downloads'.split())
except ImportError:
upload_rss = None