FB2 metadata support

This commit is contained in:
sauron 2008-05-19 10:16:40 +06:00
commit afadf5dcac
22 changed files with 2910 additions and 65 deletions

View File

@ -25,7 +25,7 @@ manual:
pictureflow : pictureflow :
mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \ mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \
cd src/calibre/gui2/pictureflow && \ cd src/calibre/gui2/pictureflow && rm *.o && \
mkdir -p .build && cd .build && rm -f * && \ mkdir -p .build && cd .build && rm -f * && \
qmake ../pictureflow-lib.pro && make && \ qmake ../pictureflow-lib.pro && make && \
cd ../PyQt && \ cd ../PyQt && \

View File

@ -3,7 +3,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Create an OSX installer ''' ''' Create an OSX installer '''
import sys, re, os, shutil, subprocess, stat import sys, re, os, shutil, subprocess, stat, glob
from setup import VERSION, APPNAME, scripts, main_modules, basenames, main_functions from setup import VERSION, APPNAME, scripts, main_modules, basenames, main_functions
from setuptools import setup from setuptools import setup
from py2app.build_app import py2app from py2app.build_app import py2app
@ -132,7 +132,13 @@ _check_symlinks_prescript()
fp = '@executable_path/../Frameworks/' fp = '@executable_path/../Frameworks/'
print 'Fixing qt dependencies for:', os.path.basename(path) print 'Fixing qt dependencies for:', os.path.basename(path)
for dep in deps: for dep in deps:
module = re.search(r'(Qt\w+?)\.framework', dep).group(1) match = re.search(r'(Qt\w+?)\.framework', dep)
if not match:
match = re.search(r'(phonon)\.framework', dep)
if not match:
print dep
raise Exception('Unknown Qt dependency')
module = match.group(1)
newpath = fp + '%s.framework/Versions/Current/%s'%(module, module) newpath = fp + '%s.framework/Versions/Current/%s'%(module, module)
cmd = ' '.join(['install_name_tool', '-change', dep, newpath, path]) cmd = ' '.join(['install_name_tool', '-change', dep, newpath, path])
subprocess.check_call(cmd, shell=True) subprocess.check_call(cmd, shell=True)
@ -157,10 +163,34 @@ _check_symlinks_prescript()
#deps = BuildAPP.qt_dependencies(path) #deps = BuildAPP.qt_dependencies(path)
def build_plugins(self):
cwd = os.getcwd()
qmake = '/Users/kovid/qt/bin/qmake'
files = []
try:
print 'Building pictureflow'
os.chdir('src/calibre/gui2/pictureflow')
for f in glob.glob('*.o'): os.unlink(f)
subprocess.check_call([qmake, 'pictureflow-lib.pro'])
subprocess.check_call(['make'])
files.append((os.path.abspath(os.path.realpath('libpictureflow.dylib')), 'libpictureflow.dylib'))
os.chdir('PyQt/.build')
subprocess.check_call(['python', '../configure.py'])
subprocess.check_call(['make'])
files.append((os.path.abspath('pictureflow.so'), 'pictureflow.so'))
subprocess.check_call(['install_name_tool', '-change', 'libpictureflow.0.dylib', '@executable_path/../Frameworks/libpictureflow.dylib', 'pictureflow.so'])
subprocess.check_call(['install_name_tool', '-change', '/System/Library/Frameworks/Python.framework/Versions/2.5/Python', '@executable_path/../Frameworks/Python.framework/Versions/2.5/Python', 'pictureflow.so'])
for i in range(2):
deps = BuildAPP.qt_dependencies(files[i][0])
BuildAPP.fix_qt_dependencies(files[i][0], deps)
return files
finally:
os.chdir(cwd)
def run(self): def run(self):
py2app.run(self) py2app.run(self)
self.add_qt_plugins()
resource_dir = os.path.join(self.dist_dir, resource_dir = os.path.join(self.dist_dir,
APPNAME + '.app', 'Contents', 'Resources') APPNAME + '.app', 'Contents', 'Resources')
frameworks_dir = os.path.join(os.path.dirname(resource_dir), 'Frameworks') frameworks_dir = os.path.join(os.path.dirname(resource_dir), 'Frameworks')
@ -178,6 +208,8 @@ _check_symlinks_prescript()
f.close() f.close()
os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\ os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\
|stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP) |stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP)
self.add_qt_plugins()
plugin_files = self.build_plugins()
print print
print 'Adding clit' print 'Adding clit'
@ -188,6 +220,13 @@ _check_symlinks_prescript()
print print
print 'Adding pdftohtml' print 'Adding pdftohtml'
os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml')) os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml'))
print 'Adding plugins'
module_dir = os.path.join(resource_dir, 'lib', 'python2.5', 'lib-dynload')
for src, dest in plugin_files:
if 'dylib' in dest:
os.link(src, os.path.join(frameworks_dir, dest))
else:
os.link(src, os.path.join(module_dir, dest))
print print
print 'Installing prescipt' print 'Installing prescipt'
sf = [os.path.basename(s) for s in all_names] sf = [os.path.basename(s) for s in all_names]
@ -231,7 +270,8 @@ def main():
'argv_emulation' : True, 'argv_emulation' : True,
'iconfile' : 'icons/library.icns', 'iconfile' : 'icons/library.icns',
'frameworks': ['libusb.dylib', 'libunrar.dylib'], 'frameworks': ['libusb.dylib', 'libunrar.dylib'],
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtSvg', 'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
'PyQt4.QtSvg',
'mechanize', 'ClientForm', 'usbobserver', 'mechanize', 'ClientForm', 'usbobserver',
'genshi', 'calibre.web.feeds.recipes.*', 'genshi', 'calibre.web.feeds.recipes.*',
'IPython.Extensions.*', 'pydoc'], 'IPython.Extensions.*', 'pydoc'],
@ -251,7 +291,7 @@ def main():
setup_requires = ['py2app'], setup_requires = ['py2app'],
) )
if auto: if auto:
subprocess.call(('sudo', 'shutdown', '-h', '+1')) subprocess.call(('sudo', 'shutdown', '-h', '+2'))
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,7 +1,7 @@
''' E-book management software''' ''' E-book management software'''
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
__version__ = '0.4.55' __version__ = '0.4.56'
__docformat__ = "epytext" __docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>" __author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
__appname__ = 'calibre' __appname__ = 'calibre'

View File

@ -155,6 +155,7 @@ class BookList(_BookList):
src = src.decode('latin1') src = src.decode('latin1')
except UnicodeDecodeError: except UnicodeDecodeError:
src = src.decode('cp1252') src = src.decode('cp1252')
src = src.replace('<cache:', '<xs1:').replace('</cache:', '</xs1:').replace('xmlns:cache', 'xmlns:xs1')
self.document = dom.parseString(src.encode('utf8')) self.document = dom.parseString(src.encode('utf8'))
self.root = self.document.documentElement self.root = self.document.documentElement
self.prefix = '' self.prefix = ''

View File

@ -40,7 +40,7 @@ def xml_to_unicode(raw, verbose=False):
return u'', encoding return u'', encoding
if isinstance(raw, unicode): if isinstance(raw, unicode):
return raw, encoding return raw, encoding
match = re.compile('^\s*<\?.*encoding=[\'"](.*?)[\'"].*\?>', re.IGNORECASE).match(raw) match = re.compile(r'<[^<>]+encoding=[\'"](.*?)[\'"][^<>]*>', re.IGNORECASE).search(raw)
if match is None: if match is None:
match = re.compile(r'<meta.*?content=[\'"].*?charset=([^\s\'"]+).*?[\'"]', re.IGNORECASE).search(raw) match = re.compile(r'<meta.*?content=[\'"].*?charset=([^\s\'"]+).*?[\'"]', re.IGNORECASE).search(raw)
if match is not None: if match is not None:

View File

@ -287,8 +287,7 @@ class HTMLConverter(object, LoggingInterface):
self.book.append(self.current_page) self.book.append(self.current_page)
for text, tb in self.extra_toc_entries: for text, tb in self.extra_toc_entries:
ascii_text = text.encode('ascii', 'ignore') self.book.addTocEntry(text, tb)
self.book.addTocEntry(ascii_text, tb)
if self.base_font_size > 0: if self.base_font_size > 0:
self.log_info('\tRationalizing font sizes...') self.log_info('\tRationalizing font sizes...')
@ -1427,6 +1426,11 @@ class HTMLConverter(object, LoggingInterface):
path = munge_paths(self.target_prefix, tag['href'])[0] path = munge_paths(self.target_prefix, tag['href'])[0]
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if ext: ext = ext[1:].lower() if ext: ext = ext[1:].lower()
enc = sys.getfilesystemencoding()
if not enc:
enc = 'utf8'
if isinstance(path, unicode):
path = path.encode(enc, 'replace')
if os.access(path, os.R_OK) and os.path.isfile(path): if os.access(path, os.R_OK) and os.path.isfile(path):
if ext in ['png', 'jpg', 'bmp', 'jpeg']: if ext in ['png', 'jpg', 'bmp', 'jpeg']:
self.process_image(path, tag_css) self.process_image(path, tag_css)

View File

@ -1,6 +1,7 @@
<ncx version="2005-1" <ncx version="2005-1"
xml:lang="en" xml:lang="en"
xmlns="http://www.daisy.org/z3986/2005/ncx/" xmlns="http://www.daisy.org/z3986/2005/ncx/"
encoding="UTF-8"
xmlns:py="http://genshi.edgewall.org/" xmlns:py="http://genshi.edgewall.org/"
> >
<head> <head>

View File

@ -7,6 +7,7 @@ from urllib import unquote
from calibre import __appname__ from calibre import __appname__
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup
from calibre.ebooks.chardet import xml_to_unicode
class NCXSoup(BeautifulStoneSoup): class NCXSoup(BeautifulStoneSoup):
@ -95,7 +96,7 @@ class TOC(list):
def read_ncx_toc(self, toc): def read_ncx_toc(self, toc):
self.base_path = os.path.dirname(toc) self.base_path = os.path.dirname(toc)
soup = NCXSoup(open(toc, 'rb').read()) soup = NCXSoup(xml_to_unicode(open(toc, 'rb').read())[0])
def process_navpoint(np, dest): def process_navpoint(np, dest):
play_order = np.get('playOrder', 1) play_order = np.get('playOrder', 1)

View File

@ -5,11 +5,11 @@ import sys, os, re, StringIO, traceback
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, \ from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, \
QByteArray, QLocale, QTranslator, QUrl QByteArray, QLocale, QTranslator, QUrl
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QTableView QIcon, QTableView, QDialogButtonBox
ORG_NAME = 'KovidsBrain' ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500' APP_UID = 'libprs500'
from calibre import __author__, islinux, iswindows, Settings from calibre import __author__, islinux, iswindows, Settings, isosx
NONE = QVariant() #: Null value to return from the data function of item models NONE = QVariant() #: Null value to return from the data function of item models
@ -339,3 +339,42 @@ def pixmap_to_data(pixmap, format='JPEG'):
buf.open(QBuffer.WriteOnly) buf.open(QBuffer.WriteOnly)
pixmap.save(buf, format) pixmap.save(buf, format)
return str(ba.data()) return str(ba.data())
class TranslatedDialogButtonBox(QDialogButtonBox):
STRINGS = {
QDialogButtonBox.Ok : (_('&OK'), QDialogButtonBox.AcceptRole),
QDialogButtonBox.Open : (_('&Open'), QDialogButtonBox.AcceptRole),
QDialogButtonBox.Save : (_('&Save'), QDialogButtonBox.AcceptRole),
QDialogButtonBox.Cancel : (_('Cancel'), QDialogButtonBox.RejectRole),
QDialogButtonBox.Close : (_('&Close'), QDialogButtonBox.RejectRole),
QDialogButtonBox.Discard : (_("&Don't Save") if isosx else _('&Discard'), QDialogButtonBox.DestructiveRole),
QDialogButtonBox.Apply : (_('&Apply'), QDialogButtonBox.ApplyRole),
QDialogButtonBox.Reset : (_('&Reset'), QDialogButtonBox.ResetRole),
QDialogButtonBox.RestoreDefaults : (_('Restore &Defaults'), QDialogButtonBox.ResetRole),
QDialogButtonBox.Help : (_('&Help'), QDialogButtonBox.HelpRole),
QDialogButtonBox.SaveAll : (_('Save &All'), QDialogButtonBox.AcceptRole),
QDialogButtonBox.Yes : (_('&Yes'), QDialogButtonBox.YesRole),
QDialogButtonBox.YesToAll : (_('Yes to &All'), QDialogButtonBox.YesRole),
QDialogButtonBox.No : (_('&No'), QDialogButtonBox.NoRole),
QDialogButtonBox.NoToAll : (_('N&o to All'), QDialogButtonBox.NoRole),
QDialogButtonBox.Abort : (_('A&bort'), QDialogButtonBox.RejectRole),
QDialogButtonBox.Retry : (_('Re&try'), QDialogButtonBox.AcceptRole),
QDialogButtonBox.Ignore : (_('I&gnore'), QDialogButtonBox.AcceptRole),
}
def setStandardButtons(self, buttons):
default = False
for i in self.STRINGS.keys():
if i & buttons:
msg, role = self.STRINGS[i]
button = self.addButton(msg, role)
if not default:
default = True
button.setDefault(True)
try:
from calibre.utils.single_qt_application import SingleApplication
except:
SingleApplication = None

View File

@ -33,11 +33,11 @@ class Matches(QAbstractTableModel):
return NONE return NONE
text = "" text = ""
if orientation == Qt.Horizontal: if orientation == Qt.Horizontal:
if section == 0: text = "Title" if section == 0: text = _("Title")
elif section == 1: text = "Author(s)" elif section == 1: text = _("Author(s)")
elif section == 2: text = "Author Sort" elif section == 2: text = _("Author Sort")
elif section == 3: text = "Publisher" elif section == 3: text = _("Publisher")
elif section == 4: text = "ISBN" elif section == 4: text = _("ISBN")
return QVariant(self.trUtf8(text)) return QVariant(self.trUtf8(text))
else: else:

View File

@ -13,6 +13,7 @@ from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog
from calibre.gui2.widgets import PythonHighlighter from calibre.gui2.widgets import PythonHighlighter
from calibre.utils import sendmail from calibre.utils import sendmail
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre import isosx
class UserProfiles(QDialog, Ui_Dialog): class UserProfiles(QDialog, Ui_Dialog):
@ -69,7 +70,7 @@ class UserProfiles(QDialog, Ui_Dialog):
pt.close() pt.close()
sendmail(subject='Recipe for '+title, sendmail(subject='Recipe for '+title,
attachments=[pt.name], attachments=[pt.name],
body='The attached file: %s is a recipe to download %s.'%(os.path.basename(pt.name), title)) body=_('Save the text below into a file named recipe.py and send the file to your friends, to allow them to use this recipe.') if isosx else _('The attached file: %s is a recipe to download %s.')%(os.path.basename(pt.name), title))

View File

@ -298,13 +298,12 @@ class BooksModel(QAbstractTableModel):
if col == 0: if col == 0:
text = self.db.title(row) text = self.db.title(row)
if text: if text:
return QVariant(BooksView.wrap(text, width=35)) return QVariant(text)
elif col == 1: elif col == 1:
au = self.db.authors(row) au = self.db.authors(row)
if au: if au:
au = au.split(',') au = au.split(',')
jau = [ BooksView.wrap(a, width=30).strip() for a in au ] return QVariant("\n".join(au))
return QVariant("\n".join(jau))
elif col == 2: elif col == 2:
size = self.db.max_size(row) size = self.db.max_size(row)
if size: if size:
@ -321,7 +320,7 @@ class BooksModel(QAbstractTableModel):
elif col == 5: elif col == 5:
pub = self.db.publisher(row) pub = self.db.publisher(row)
if pub: if pub:
return QVariant(BooksView.wrap(pub, 20)) return QVariant(pub)
elif col == 6: elif col == 6:
tags = self.db.tags(row) tags = self.db.tags(row)
if tags: if tags:

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, textwrap, collections, traceback, shutil, time import os, sys, textwrap, collections, traceback, shutil, time
from xml.parsers.expat import ExpatError
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \ from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QVariant, QThread, QString QVariant, QThread, QString
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \ from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
@ -16,7 +16,8 @@ from calibre.devices.interface import Device
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
initialize_file_icon_provider, question_dialog,\ initialize_file_icon_provider, question_dialog,\
pixmap_to_data, choose_dir, ORG_NAME, \ pixmap_to_data, choose_dir, ORG_NAME, \
qstring_to_unicode, set_sidebar_directories qstring_to_unicode, set_sidebar_directories, \
SingleApplication
from calibre import iswindows, isosx from calibre import iswindows, isosx
from calibre.library.database import LibraryDatabase from calibre.library.database import LibraryDatabase
from calibre.gui2.update import CheckForUpdates from calibre.gui2.update import CheckForUpdates
@ -55,8 +56,13 @@ class Main(MainWindow, Ui_MainWindow):
p.end() p.end()
self.default_thumbnail = (pixmap.width(), pixmap.height(), pixmap_to_data(pixmap)) self.default_thumbnail = (pixmap.width(), pixmap.height(), pixmap_to_data(pixmap))
def __init__(self, parent=None): def __init__(self, single_instance, parent=None):
MainWindow.__init__(self, parent) MainWindow.__init__(self, parent)
self.single_instance = single_instance
if self.single_instance is not None:
self.connect(self.single_instance, SIGNAL('message_received(PyQt_PyObject)'),
self.another_instance_wants_to_talk)
Ui_MainWindow.__init__(self) Ui_MainWindow.__init__(self)
self.setupUi(self) self.setupUi(self)
self.setWindowTitle(__appname__) self.setWindowTitle(__appname__)
@ -75,6 +81,7 @@ class Main(MainWindow, Ui_MainWindow):
self.tb_wrapper = textwrap.TextWrapper(width=40) self.tb_wrapper = textwrap.TextWrapper(width=40)
self.device_connected = False self.device_connected = False
self.viewers = collections.deque() self.viewers = collections.deque()
####################### Location View ######################## ####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected) self.location_selected)
@ -82,7 +89,8 @@ class Main(MainWindow, Ui_MainWindow):
self.location_view.location_changed) self.location_view.location_changed)
####################### Vanity ######################## ####################### Vanity ########################
self.vanity_template = qstring_to_unicode(self.vanity.text().arg(__version__)).replace('%2', '%(version)s').replace('%3', '%(device)s') self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__)
self.vanity_template += _('<b>%s</b>: %s by <b>Kovid Goyal %%(version)s</b><br>%%(device)s</p>')%(__appname__, __version__)
self.latest_version = ' ' self.latest_version = ' '
self.vanity.setText(self.vanity_template%dict(version=' ', device=' ')) self.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
self.device_info = ' ' self.device_info = ' '
@ -198,6 +206,13 @@ class Main(MainWindow, Ui_MainWindow):
self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds()) self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds())
def another_instance_wants_to_talk(self, msg):
if msg.startswith('launched:'):
self.setWindowState(self.windowState() & ~Qt.WindowMinimized|Qt.WindowActive)
self.show()
self.raise_()
self.activateWindow()
def current_view(self): def current_view(self):
'''Convenience method that returns the currently visible view ''' '''Convenience method that returns the currently visible view '''
@ -258,7 +273,8 @@ class Main(MainWindow, Ui_MainWindow):
Called once metadata has been read for all books on the device. Called once metadata has been read for all books on the device.
''' '''
if exception: if exception:
if 'not well-formed' in str(exception): print exception, type(exception)
if isinstance(exception, ExpatError):
error_dialog(self, _('Device database corrupted'), error_dialog(self, _('Device database corrupted'),
_(''' _('''
<p>The database of books on the reader is corrupted. Try the following: <p>The database of books on the reader is corrupted. Try the following:
@ -701,10 +717,6 @@ class Main(MainWindow, Ui_MainWindow):
warning_dialog(self, 'Could not convert some books', msg).exec_() warning_dialog(self, 'Could not convert some books', msg).exec_()
def set_conversion_defaults(self, checked): def set_conversion_defaults(self, checked):
d = LRFSingleDialog(self, None, None) d = LRFSingleDialog(self, None, None)
d.exec_() d.exec_()
@ -1057,13 +1069,18 @@ def main(args=sys.argv):
app = QApplication(args) app = QApplication(args)
QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_UID) QCoreApplication.setApplicationName(APP_UID)
if not singleinstance('mainGUI'): single_instance = None if SingleApplication is None else SingleApplication('calibre GUI')
if not singleinstance('calibre GUI'):
if single_instance is not None and single_instance.is_running() and \
single_instance.send_message('launched:'+''.join(sys.argv)):
return 0
QMessageBox.critical(None, 'Cannot Start '+__appname__, QMessageBox.critical(None, 'Cannot Start '+__appname__,
'<p>%s is already running.</p>'%__appname__) '<p>%s is already running.</p>'%__appname__)
return 1 return 1
initialize_file_icon_provider() initialize_file_icon_provider()
try: try:
main = Main() main = Main(single_instance)
except DatabaseLocked, err: except DatabaseLocked, err:
QMessageBox.critical(None, 'Cannot Start '+__appname__, QMessageBox.critical(None, 'Cannot Start '+__appname__,
'<p>Another program is using the database. <br/>Perhaps %s is already running?<br/>If not try deleting the file %s'%(__appname__, err.lock_file_path)) '<p>Another program is using the database. <br/>Perhaps %s is already running?<br/>If not try deleting the file %s'%(__appname__, err.lock_file_path))

View File

@ -102,10 +102,7 @@
</size> </size>
</property> </property>
<property name="text" > <property name="text" >
<string>&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css"> <string></string>
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For help visit &lt;a href="http://__appname__.kovidgoyal.net/user_manual">&lt;span style=" text-decoration: underline; color:#0000ff;">__appname__.kovidgoyal.net&lt;/span>&lt;/a>&lt;br />&lt;br />&lt;span style=" font-weight:600;">__appname__&lt;/span>: %1 by &lt;span style=" font-weight:600;">Kovid Goyal&lt;/span> %2&lt;br />%3&lt;/p>&lt;/body>&lt;/html></string>
</property> </property>
<property name="textFormat" > <property name="textFormat" >
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>

View File

@ -39,7 +39,9 @@ def build_forms(forms):
dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc') dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc')
dat = dat.replace('from library import', 'from calibre.gui2.library import') dat = dat.replace('from library import', 'from calibre.gui2.library import')
dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import') dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import')
dat += '\nfrom calibre.gui2 import TranslatedDialogButtonBox'
dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)', re.DOTALL).sub(r'_("\1")', dat) dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)', re.DOTALL).sub(r'_("\1")', dat)
dat = re.compile(r'QtGui.QDialogButtonBox').sub('TranslatedDialogButtonBox', dat)
open(compiled_form, 'wb').write(dat) open(compiled_form, 'wb').write(dat)

View File

@ -30,8 +30,10 @@ makefile = pyqtconfig.QtGuiModuleMakefile (
# Add the library we are wrapping. The name doesn't include any platform # Add the library we are wrapping. The name doesn't include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the # specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows). # ".dll" extension on Windows).
makefile.extra_lib_dirs = ['../../.build', '..\\..\\release'] makefile.extra_lib_dirs = ['../../.build', '..\\..\\release', '../../']
makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform else "pictureflow"] makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform and 'darwin' not in sys.platform else "pictureflow"]
makefile.extra_cflags = ['-arch i386', '-arch ppc'] if 'darwin' in sys.platform else []
makefile.extra_cxxflags = makefile.extra_cflags
if 'linux' in sys.platform: if 'linux' in sys.platform:
makefile.extra_lflags = ['-Wl,--rpath=.'] makefile.extra_lflags = ['-Wl,--rpath=.']

View File

@ -9,7 +9,7 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \
QAbstractItemDelegate, QStyle QAbstractItemDelegate, QStyle
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QRect, SIGNAL, \ from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QRect, SIGNAL, \
QObject, QRegExp, QSize, QRectF QObject, QRegExp, QRectF
from calibre.gui2.jobs import DetailView from calibre.gui2.jobs import DetailView
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
@ -18,6 +18,8 @@ from calibre import fit_image, get_font_families, Settings
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \ from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
set_filename_pat set_filename_pat
class FilenamePattern(QWidget, Ui_Form): class FilenamePattern(QWidget, Ui_Form):
def __init__(self, parent): def __init__(self, parent):
@ -83,7 +85,7 @@ class LocationDelegate(QAbstractItemDelegate):
def __init__(self): def __init__(self):
QAbstractItemDelegate.__init__(self) QAbstractItemDelegate.__init__(self)
self.icon_rect = QRect(0, 10, 150, 45) self.icon_rect = QRect(0, 10, 150, 45)
self.buffer = 0 self.buffer = 5
def get_rects(self, index, option): def get_rects(self, index, option):
row = index.row() row = index.row()
@ -98,33 +100,21 @@ class LocationDelegate(QAbstractItemDelegate):
return irect.united(trect).size() return irect.united(trect).size()
def paint(self, painter, option, index): def paint(self, painter, option, index):
selected = bool(option.state & QStyle.State_Selected)
active = bool(option.state & QStyle.State_Active)
font = QFont() font = QFont()
font.setBold(True) font.setPointSize(9)
font.setPointSize(8)
mode = QIcon.Active if active else QIcon.Selected if selected else QIcon.Normal
icon = QIcon(index.model().data(index, Qt.DecorationRole)) icon = QIcon(index.model().data(index, Qt.DecorationRole))
highlight = getattr(index.model(), 'highlight_row', -1) == index.row() highlight = getattr(index.model(), 'highlight_row', -1) == index.row()
text = index.model().data(index, Qt.DisplayRole).toString() text = index.model().data(index, Qt.DisplayRole).toString()
painter.save() painter.save()
irect, trect = self.get_rects(index, option) irect, trect = self.get_rects(index, option)
mode = QIcon.Normal
if highlight: if highlight:
font.setItalic(True) font.setBold(True)
mode = QIcon.Active
painter.setFont(font) painter.setFont(font)
icon.paint(painter, irect, Qt.AlignHCenter|Qt.AlignTop, mode, QIcon.On) icon.paint(painter, irect, Qt.AlignHCenter|Qt.AlignTop, mode, QIcon.On)
if selected:
brect = painter.drawText(QRectF(trect), Qt.AlignTop|Qt.AlignHCenter, text)
brect.adjust(-3, -0, 3, 0)
painter.fillRect(brect, option.palette.highlight())
painter.save()
painter.setPen(Qt.DotLine)
painter.drawRect(brect)
painter.restore()
painter.setBrush(option.palette.highlightedText())
else:
painter.setBrush(option.palette.text())
painter.drawText(QRectF(trect), Qt.AlignTop|Qt.AlignHCenter, text) painter.drawText(QRectF(trect), Qt.AlignTop|Qt.AlignHCenter, text)
painter.restore() painter.restore()
@ -139,6 +129,11 @@ class LocationModel(QAbstractListModel):
_('Card\n%s available')] _('Card\n%s available')]
self.free = [-1, -1] self.free = [-1, -1]
self.highlight_row = 0 self.highlight_row = 0
self.tooltips = [
_('Click to see the list of books available on your computer'),
_('Click to see the list of books in the main memory of your reader'),
_('Click to see the list of books on the storage card in your reader')
]
def rowCount(self, parent): def rowCount(self, parent):
return 1 + sum([1 for i in self.free if i >= 0]) return 1 + sum([1 for i in self.free if i >= 0])
@ -152,6 +147,8 @@ class LocationModel(QAbstractListModel):
data = QVariant(text) data = QVariant(text)
elif role == Qt.DecorationRole: elif role == Qt.DecorationRole:
data = self.icons[row] data = self.icons[row]
elif role == Qt.ToolTipRole:
return QVariant(self.tooltips[row])
return data return data
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
@ -177,6 +174,7 @@ class LocationView(QListView):
QObject.connect(self.selectionModel(), SIGNAL('currentChanged(QModelIndex, QModelIndex)'), self.current_changed) QObject.connect(self.selectionModel(), SIGNAL('currentChanged(QModelIndex, QModelIndex)'), self.current_changed)
self.delegate = LocationDelegate() self.delegate = LocationDelegate()
self.setItemDelegate(self.delegate) self.setItemDelegate(self.delegate)
self.setCursor(Qt.PointingHandCursor)
def current_changed(self, current, previous): def current_changed(self, current, previous):
i = current.row() i = current.row()

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,10 @@ def sendmail(recipient='', subject='', attachments=[], body=''):
pt.write(open(attachments[0], 'rb').read()) pt.write(open(attachments[0], 'rb').read())
pt.close() pt.close()
subprocess.check_call('open -t '+pt.name, shell=True) try:
subprocess.call('open -t '+pt.name, shell=True)
except: # For some reason making this call leads to a system call interrupted error
pass
else: else:
body = '"' + body.replace('"', '\\"') + '"' body = '"' + body.replace('"', '\\"') + '"'
subject = '"' + subject.replace('"', '\\"') + '"' subject = '"' + subject.replace('"', '\\"') + '"'

View File

@ -0,0 +1,162 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Enforces running of only a single application instance and allows for messaging between
applications using a local socket.
'''
import atexit
from PyQt4.QtCore import QByteArray, QDataStream, QIODevice, SIGNAL, QObject, Qt
from PyQt4.QtNetwork import QLocalSocket, QLocalServer
timeout_read = 5000
timeout_connect = 500
def write_message(socket, message, timeout = 5000):
block = QByteArray()
out = QDataStream(block, QIODevice.WriteOnly)
out.writeInt32(0)
out.writeString(message)
out.device().seek(0)
out.writeInt32(len(message))
socket.write(block)
return getattr(socket, 'state', lambda : None)() == QLocalSocket.ConnectedState and \
bool(socket.waitForBytesWritten(timeout))
def read_message(socket):
if getattr(socket, 'state', lambda : None)() != QLocalSocket.ConnectedState:
return ''
while socket.bytesAvailable() < 4:
if not socket.waitForReadyRead(timeout_read):
return ''
message = ''
ins = QDataStream(socket)
block_size = ins.readInt32()
while socket.bytesAvailable() < block_size:
if not socket.waitForReadyRead(timeout_read):
return message
return str(ins.readString())
class Connection(QObject):
def __init__(self, socket, name):
QObject.__init__(self)
self.socket = socket
self.name = name
self.magic = self.name + ':'
self.connect(self.socket, SIGNAL('readyRead()'), self.read_msg, Qt.QueuedConnection)
self.write_succeeded = write_message(self.socket, self.name)
self.connect(self.socket, SIGNAL('disconnected()'), self.disconnected)
if not self.write_succeeded:
self.socket.abort()
def read_msg(self):
while self.socket.bytesAvailable() > 0:
msg = read_message(self.socket)
if msg.startswith(self.magic):
self.emit(SIGNAL('message_received(PyQt_PyObject)'), msg[len(self.magic):])
def disconnected(self):
self.emit(SIGNAL('disconnected()'))
class LocalServer(QLocalServer):
def __init__(self, server_id, parent=None):
QLocalServer.__init__(self, parent)
self.server_id = str(server_id)
self.mr = lambda x : self.emit(SIGNAL('message_received(PyQt_PyObject)'), x)
self.connections = []
self.connect(self, SIGNAL('newConnection()'), self.new_connection)
def new_connection(self):
socket = self.nextPendingConnection()
conn = Connection(socket, self.server_id)
if conn.socket.state() != QLocalSocket.UnconnectedState:
self.connect(conn, SIGNAL('message_received(PyQt_PyObject)'), self.mr)
self.connect(conn, SIGNAL('disconnected()'), self.free)
self.connections.append(conn)
def free(self):
pop = []
for conn in self.connections:
if conn.socket.state() == QLocalSocket.UnconnectedState:
pop.append(conn)
for conn in pop:
self.connections.remove(conn)
class SingleApplication(QObject):
def __init__(self, name, parent=None, server_name='calibre_server'):
QObject.__init__(self, parent)
self.name = name
self.server_name = server_name
self.running = False
self.mr = lambda x : self.emit(SIGNAL('message_received(PyQt_PyObject)'), x)
# Check if server is already running
self.socket = QLocalSocket(self)
self.socket.connectToServer(self.server_name)
if self.socket.waitForConnected(timeout_connect):
msg = read_message(self.socket)
if msg == self.name:
self.running = True
# Start server
self.server = None
if not self.running:
self.socket.abort()
self.socket = None
self.server = LocalServer(self.name, self)
self.connect(self.server, SIGNAL('message_received(PyQt_PyObject)'),
self.mr, Qt.QueuedConnection)
if not self.server.listen(self.server_name):
if not self.server.listen(self.server_name):
self.server = None
if self.server is not None:
atexit.register(self.server.close)
def is_running(self, name=None):
return self.running if name is None else SingleApplication().is_running()
def send_message(self, msg, timeout=3000):
return self.running and write_message(self.socket, self.name+':'+msg, timeout)
if __name__ == '__main__':
from PyQt4.Qt import QWidget, QApplication
class Test(QWidget):
def __init__(self, sa):
QWidget.__init__(self)
self.sa = sa
self.connect(sa, SIGNAL('message_received(PyQt_PyObject)'), self.mr)
def mr(self, msg):
print 'Message received:', msg
app = QApplication([])
app.connect(app, SIGNAL('lastWindowClosed()'), app.quit)
sa = SingleApplication('test SA')
if sa.is_running():
sa.send_message('test message')
else:
widget = Test(sa)
widget.show()
app.exec_()

View File

@ -62,9 +62,9 @@ def compile_recipe(src):
enc = match.group(1) if match else 'utf-8' enc = match.group(1) if match else 'utf-8'
src = src.decode(enc) src = src.decode(enc)
f = open(temp, 'wb') f = open(temp, 'wb')
src = '# coding=utf-8\n' + src
src = 'from %s.web.feeds.news import BasicNewsRecipe, AutomaticNewsRecipe\n'%__appname__ + src src = 'from %s.web.feeds.news import BasicNewsRecipe, AutomaticNewsRecipe\n'%__appname__ + src
src = 'from %s.ebooks.lrf.web.profiles import DefaultProfile, FullContentProfile\n'%__appname__ + src src = 'from %s.ebooks.lrf.web.profiles import DefaultProfile, FullContentProfile\n'%__appname__ + src
src = '# coding: utf-8\n' + src
f.write(src.replace('from libprs500', 'from calibre').encode('utf-8')) f.write(src.replace('from libprs500', 'from calibre').encode('utf-8'))
f.close() f.close()
module = imp.find_module(temp.namebase, [temp.dirname()]) module = imp.find_module(temp.namebase, [temp.dirname()])

View File

@ -250,7 +250,10 @@ class RecursiveFetcher(object, LoggingInterface):
self.log_debug('Error: %s', str(err), exc_info=True) self.log_debug('Error: %s', str(err), exc_info=True)
continue continue
c += 1 c += 1
imgpath = os.path.join(diskpath, sanitize_file_name('img'+str(c)+ext)) fname = sanitize_file_name('img'+str(c)+ext)
if isinstance(fname, unicode):
fname = fname.encode('ascii', 'replace')
imgpath = os.path.join(diskpath, fname)
with self.imagemap_lock: with self.imagemap_lock:
self.imagemap[iurl] = imgpath self.imagemap[iurl] = imgpath
open(imgpath, 'wb').write(f.read()) open(imgpath, 'wb').write(f.read())