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 :
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 * && \
qmake ../pictureflow-lib.pro && make && \
cd ../PyQt && \

View File

@ -3,7 +3,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' 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 setuptools import setup
from py2app.build_app import py2app
@ -132,7 +132,13 @@ _check_symlinks_prescript()
fp = '@executable_path/../Frameworks/'
print 'Fixing qt dependencies for:', os.path.basename(path)
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)
cmd = ' '.join(['install_name_tool', '-change', dep, newpath, path])
subprocess.check_call(cmd, shell=True)
@ -156,11 +162,35 @@ _check_symlinks_prescript()
#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):
py2app.run(self)
self.add_qt_plugins()
resource_dir = os.path.join(self.dist_dir,
APPNAME + '.app', 'Contents', 'Resources')
frameworks_dir = os.path.join(os.path.dirname(resource_dir), 'Frameworks')
@ -178,6 +208,8 @@ _check_symlinks_prescript()
f.close()
os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\
|stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP)
self.add_qt_plugins()
plugin_files = self.build_plugins()
print
print 'Adding clit'
@ -188,6 +220,13 @@ _check_symlinks_prescript()
print
print 'Adding 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 'Installing prescipt'
sf = [os.path.basename(s) for s in all_names]
@ -231,7 +270,8 @@ def main():
'argv_emulation' : True,
'iconfile' : 'icons/library.icns',
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtSvg',
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
'PyQt4.QtSvg',
'mechanize', 'ClientForm', 'usbobserver',
'genshi', 'calibre.web.feeds.recipes.*',
'IPython.Extensions.*', 'pydoc'],
@ -251,7 +291,7 @@ def main():
setup_requires = ['py2app'],
)
if auto:
subprocess.call(('sudo', 'shutdown', '-h', '+1'))
subprocess.call(('sudo', 'shutdown', '-h', '+2'))
return 0
if __name__ == '__main__':

View File

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

View File

@ -155,6 +155,7 @@ class BookList(_BookList):
src = src.decode('latin1')
except UnicodeDecodeError:
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.root = self.document.documentElement
self.prefix = ''

View File

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

View File

@ -287,8 +287,7 @@ class HTMLConverter(object, LoggingInterface):
self.book.append(self.current_page)
for text, tb in self.extra_toc_entries:
ascii_text = text.encode('ascii', 'ignore')
self.book.addTocEntry(ascii_text, tb)
self.book.addTocEntry(text, tb)
if self.base_font_size > 0:
self.log_info('\tRationalizing font sizes...')
@ -1427,6 +1426,11 @@ class HTMLConverter(object, LoggingInterface):
path = munge_paths(self.target_prefix, tag['href'])[0]
ext = os.path.splitext(path)[1]
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 ext in ['png', 'jpg', 'bmp', 'jpeg']:
self.process_image(path, tag_css)

View File

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

View File

@ -7,6 +7,7 @@ from urllib import unquote
from calibre import __appname__
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup
from calibre.ebooks.chardet import xml_to_unicode
class NCXSoup(BeautifulStoneSoup):
@ -95,7 +96,7 @@ class TOC(list):
def read_ncx_toc(self, 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):
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, \
QByteArray, QLocale, QTranslator, QUrl
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QTableView
QIcon, QTableView, QDialogButtonBox
ORG_NAME = 'KovidsBrain'
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
@ -339,3 +339,42 @@ def pixmap_to_data(pixmap, format='JPEG'):
buf.open(QBuffer.WriteOnly)
pixmap.save(buf, format)
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
text = ""
if orientation == Qt.Horizontal:
if section == 0: text = "Title"
elif section == 1: text = "Author(s)"
elif section == 2: text = "Author Sort"
elif section == 3: text = "Publisher"
elif section == 4: text = "ISBN"
if section == 0: text = _("Title")
elif section == 1: text = _("Author(s)")
elif section == 2: text = _("Author Sort")
elif section == 3: text = _("Publisher")
elif section == 4: text = _("ISBN")
return QVariant(self.trUtf8(text))
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.utils import sendmail
from calibre.ptempfile import PersistentTemporaryFile
from calibre import isosx
class UserProfiles(QDialog, Ui_Dialog):
@ -69,7 +70,7 @@ class UserProfiles(QDialog, Ui_Dialog):
pt.close()
sendmail(subject='Recipe for '+title,
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:
text = self.db.title(row)
if text:
return QVariant(BooksView.wrap(text, width=35))
return QVariant(text)
elif col == 1:
au = self.db.authors(row)
if au:
au = au.split(',')
jau = [ BooksView.wrap(a, width=30).strip() for a in au ]
return QVariant("\n".join(jau))
return QVariant("\n".join(au))
elif col == 2:
size = self.db.max_size(row)
if size:
@ -321,7 +320,7 @@ class BooksModel(QAbstractTableModel):
elif col == 5:
pub = self.db.publisher(row)
if pub:
return QVariant(BooksView.wrap(pub, 20))
return QVariant(pub)
elif col == 6:
tags = self.db.tags(row)
if tags:

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, textwrap, collections, traceback, shutil, time
from xml.parsers.expat import ExpatError
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QVariant, QThread, QString
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, \
initialize_file_icon_provider, question_dialog,\
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.library.database import LibraryDatabase
from calibre.gui2.update import CheckForUpdates
@ -55,8 +56,13 @@ class Main(MainWindow, Ui_MainWindow):
p.end()
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)
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)
self.setupUi(self)
self.setWindowTitle(__appname__)
@ -75,6 +81,7 @@ class Main(MainWindow, Ui_MainWindow):
self.tb_wrapper = textwrap.TextWrapper(width=40)
self.device_connected = False
self.viewers = collections.deque()
####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected)
@ -82,7 +89,8 @@ class Main(MainWindow, Ui_MainWindow):
self.location_view.location_changed)
####################### 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.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
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())
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):
'''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.
'''
if exception:
if 'not well-formed' in str(exception):
print exception, type(exception)
if isinstance(exception, ExpatError):
error_dialog(self, _('Device database corrupted'),
_('''
<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_()
def set_conversion_defaults(self, checked):
d = LRFSingleDialog(self, None, None)
d.exec_()
@ -1057,13 +1069,18 @@ def main(args=sys.argv):
app = QApplication(args)
QCoreApplication.setOrganizationName(ORG_NAME)
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__,
'<p>%s is already running.</p>'%__appname__)
return 1
initialize_file_icon_provider()
try:
main = Main()
main = Main(single_instance)
except DatabaseLocked, err:
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))

View File

@ -102,10 +102,7 @@
</size>
</property>
<property name="text" >
<string>&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
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>
<string></string>
</property>
<property name="textFormat" >
<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('from library import', 'from calibre.gui2.library 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.QDialogButtonBox').sub('TranslatedDialogButtonBox', 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
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_lib_dirs = ['../../.build', '..\\..\\release']
makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform else "pictureflow"]
makefile.extra_lib_dirs = ['../../.build', '..\\..\\release', '../../']
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:
makefile.extra_lflags = ['-Wl,--rpath=.']

View File

@ -9,7 +9,7 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \
QAbstractItemDelegate, QStyle
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 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, \
set_filename_pat
class FilenamePattern(QWidget, Ui_Form):
def __init__(self, parent):
@ -83,7 +85,7 @@ class LocationDelegate(QAbstractItemDelegate):
def __init__(self):
QAbstractItemDelegate.__init__(self)
self.icon_rect = QRect(0, 10, 150, 45)
self.buffer = 0
self.buffer = 5
def get_rects(self, index, option):
row = index.row()
@ -98,33 +100,21 @@ class LocationDelegate(QAbstractItemDelegate):
return irect.united(trect).size()
def paint(self, painter, option, index):
selected = bool(option.state & QStyle.State_Selected)
active = bool(option.state & QStyle.State_Active)
font = QFont()
font.setBold(True)
font.setPointSize(8)
mode = QIcon.Active if active else QIcon.Selected if selected else QIcon.Normal
font.setPointSize(9)
icon = QIcon(index.model().data(index, Qt.DecorationRole))
highlight = getattr(index.model(), 'highlight_row', -1) == index.row()
text = index.model().data(index, Qt.DisplayRole).toString()
painter.save()
irect, trect = self.get_rects(index, option)
mode = QIcon.Normal
if highlight:
font.setItalic(True)
font.setBold(True)
mode = QIcon.Active
painter.setFont(font)
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.restore()
@ -138,7 +128,12 @@ class LocationModel(QAbstractListModel):
_('Reader\n%s available'),
_('Card\n%s available')]
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):
return 1 + sum([1 for i in self.free if i >= 0])
@ -152,6 +147,8 @@ class LocationModel(QAbstractListModel):
data = QVariant(text)
elif role == Qt.DecorationRole:
data = self.icons[row]
elif role == Qt.ToolTipRole:
return QVariant(self.tooltips[row])
return data
def headerData(self, section, orientation, role):
@ -176,7 +173,8 @@ class LocationView(QListView):
self.reset()
QObject.connect(self.selectionModel(), SIGNAL('currentChanged(QModelIndex, QModelIndex)'), self.current_changed)
self.delegate = LocationDelegate()
self.setItemDelegate(self.delegate)
self.setItemDelegate(self.delegate)
self.setCursor(Qt.PointingHandCursor)
def current_changed(self, current, previous):
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.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:
body = '"' + body.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'
src = src.decode(enc)
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.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.close()
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)
continue
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:
self.imagemap[iurl] = imgpath
open(imgpath, 'wb').write(f.read())