Sync to trunk.

This commit is contained in:
John Schember 2011-01-28 20:05:40 -05:00
commit b435e375e6
93 changed files with 30746 additions and 18437 deletions

View File

@ -4,12 +4,137 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
#- version: ?.?.?
# date: 2011-??-??
#
# new features:
# - title:
#
# bug fixes:
# - title:
#
# improved recipes:
# -
#
# new recipes:
# - title:
- version: 0.7.43
date: 2011-01-28
new features:
- title: "Ask for confirmation when stopping running jobs"
tickets: [3101]
- title: "Combine the database integrity check and library check into a single menu item. Also nicer implementation of the db integrity check."
- title: "BiBTeX Catalog: Add option to include file paths in the catalog."
tickets: [8589]
- title: "Create 'generic' output profiles and generic devices in the welcome wizard"
- title: "Bulk metadata edit: Custom column widgets all have an apply checkbox next to them."
- title: "Only use LibraryThing to download metadata if the user provides a library thing username and password. Since LT doesn't like web scraping"
- title: "Allow renaming of user categories in the manage categories dialog. Also allow searching for books in a category from the Tag Browser by right clicking ona a category"
- title: "Folder device plugin: Add option to disable the use of sub folders"
- title: "Allow saving/loading of search and replace expressions in the bulk metadata edit dialog."
- title: "Remeber previously used regular expression in the add books preferences dialog"
- title: "Search and replace wizard: Cache the previously used input document."
- title: "Pressing Esc clears the current search in the main book list"
- title: "Preselct right formats when using send specific format to device"
tickets: [7834]
- title: "Regex wizard gets find next and previous match buttons"
tickets: [4486]
bug fixes:
- title: "Do not allow customization of user interface plugins until calibre is restarted"
tickets: [8621]
- title: "EPUB Output: When using preserve cover aspect ratio, use the actual image sizes in the SVG template as otherwise ADE doesn't fully preserve the aspect ratio"
- title: "Fix completion on a word with a trailing space causing the first letter to be duplicated in the edit metadata dialog"
- title: "PML Input: PML x and Xn tags don't indent properly in TOC. Also handle invalid T markup and retain soft scene breaks"
tickets: [6194, 8565]
- title: "TXT Input: Retain whitespace at the beginning of lines. Don't preserve spaces in heuristic processing. Detect and retain soft scene breaks."
- title: "Fix Adding empty book - cover browser doesn't update"
tickets: [8557]
- title: "When generating author sort string ignore trailing Inc."
tickets: [8539]
- title: "When converting HTML/ZIP files do not leave temporary files that are only deleted on application shutdown."
tickets: [8597]
- title: "Don't crash if the prefs stored in the db are corrupted"
- title: "Catalog generation: Do not use inline-block CSS as apparently Adobe Digital Editions cannot handle it."
tickets: [8566]
- title: "Fix extra spaces being inserted into TOC title when reading TOC from OPF guide element."
tickets: [8569]
- title: "Remember window size for bulk metadata edit and catalog generation dialogs"
tickets: [8525]
- title: "Heuristics, italicize common cases: Enhance pattern matching to match punctuation after pattern."
- title: "Fix regression in converting HTML files that have ASCII-encoded non unicode characters inside their <style> tags. Apparently Word generates these."
tickets: [8494]
improved recipes:
- Calgary Herald
- The Economist
- New Yorker
- Heise
- HNA
- ZDNet
- NRC Handelsblad
new recipes:
- title: "SPIN Magazine"
author: Quistopher
- title: "Caps n Babes"
author: skyhawker
- title: "Leduc"
author: Brian Hahn
- title: "David Bravo's Blog, La Nueva Espana, 20 Minutos and La Tribuna de Talavera"
author: Luis Hernandez
- title: "Sinfest"
author: nadid
- title: "Various Czech news sources"
author: FunThomas
- title: "tportal.h"
author: Darko Miletic
- title: "Everett Herald"
author: "77jag5"
- title: "Roger Ebert"
author: Shane Erstad
- version: 0.7.42 - version: 0.7.42
date: 2011-01-21 date: 2011-01-21
new features: new features:
- title: "0.7.42 is a re-release of 0.7.41, because conversion to MOBI was broken in 0.7.41"
- title: "Conversions: Replace the remove header/footer options with a more geenric search replace option, that allows you to not only remove but also replace text" - title: "Conversions: Replace the remove header/footer options with a more geenric search replace option, that allows you to not only remove but also replace text"
- title: "Conversion: The preprocess html option has now become a new 'Heuristic Processing' option which allows you to control exactly which heuristics are used" - title: "Conversion: The preprocess html option has now become a new 'Heuristic Processing' option which allows you to control exactly which heuristics are used"

View File

@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe):
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
encoding = 'latin1'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
extra_css = ''' extra_css = '''
@ -97,7 +98,9 @@ class CanWestPaper(BasicNewsRecipe):
atag = h1tag.find('a',href=True) atag = h1tag.find('a',href=True)
if not atag: if not atag:
continue continue
url = self.url_prefix+'/news/todays-paper/'+atag['href'] url = atag['href']
if not url.startswith('http:'):
url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key) #self.log("Section %s" % key)
#self.log("url %s" % url) #self.log("url %s" % url)
title = self.tag_to_string(atag,False) title = self.tag_to_string(atag,False)

View File

@ -0,0 +1,11 @@
from calibre.web.feeds.news import BasicNewsRecipe
class CapesnBabesRecipe(BasicNewsRecipe):
title = u'Capes n Babes'
language = 'en'
description = 'The Capes n Babes comic Blog'
__author__ = 'skyhawker'
oldest_article = 31
max_articles_per_feed = 100
use_embedded_content = True
feeds = [(u'Capes & Babes', u'feed://www.capesnbabes.com/feed/')]

View File

@ -26,7 +26,7 @@ class BBC(BasicNewsRecipe):
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }' extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
feeds = [ feeds = [
('Interviews', 'http://www.avclub.com/feed/interview/'), ('TV', 'http://www.avclub.com/feed/tv/'),
('AV Club Daily', 'http://www.avclub.com/feed/daily'), ('AV Club Daily', 'http://www.avclub.com/feed/daily'),
('Film', 'http://www.avclub.com/feed/film/'), ('Film', 'http://www.avclub.com/feed/film/'),
('Music', 'http://www.avclub.com/feed/music/'), ('Music', 'http://www.avclub.com/feed/music/'),

View File

@ -0,0 +1,15 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1296179411(BasicNewsRecipe):
title = u'SPIN Magzine'
__author__ = 'Quistopher'
language = 'en'
oldest_article = 7
max_articles_per_feed = 100
feeds = [
(u'Daily Noise Blog | SPIN.com', u'http://www.spin.com/blog/feed'),
(u'It Happened Last Night | SPIN.com', u'http://www.spin.com/it-happened-last-night/feed'),
(u'Album Reviews | SPIN.com', u'http://www.spin.com/album-reviews/feed')
]

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.42' __version__ = '0.7.43'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -19,7 +19,8 @@ class ANDROID(USBMS):
VENDOR_ID = { VENDOR_ID = {
# HTC # HTC
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9 0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100,
0x0227, 0x0226], 0x0ff9
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226], : [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]}, 0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},

View File

@ -141,8 +141,8 @@ class CoverManager(object):
if width is None or height is None: if width is None or height is None:
self.log.warning('Failed to read cover dimensions') self.log.warning('Failed to read cover dimensions')
width, height = 600, 800 width, height = 600, 800
if self.preserve_aspect_ratio: #if self.preserve_aspect_ratio:
width, height = 600, 800 # width, height = 600, 800
self.svg_template = self.svg_template.replace('__viewbox__', self.svg_template = self.svg_template.replace('__viewbox__',
'0 0 %d %d'%(width, height)) '0 0 %d %d'%(width, height))
self.svg_template = self.svg_template.replace('__width__', self.svg_template = self.svg_template.replace('__width__',

View File

@ -47,7 +47,7 @@ class PDFOutput(OutputFormatPlugin):
OptionRecommendation(name='preserve_cover_aspect_ratio', OptionRecommendation(name='preserve_cover_aspect_ratio',
recommended_value=False, recommended_value=False,
help=_('Preserve the aspect ratio of the cover, instead' help=_('Preserve the aspect ratio of the cover, instead'
' of stretching it to fill the ull first page of the' ' of stretching it to fill the full first page of the'
' generated pdf.') ' generated pdf.')
), ),
]) ])

View File

@ -197,14 +197,10 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
return d.exec_() return d.exec_()
return d return d
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False, def question_dialog(parent, title, msg, det_msg='', show_copy_button=False):
buttons=None, yes_button=None):
from calibre.gui2.dialogs.message_box import MessageBox from calibre.gui2.dialogs.message_box import MessageBox
d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent, d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
show_copy_button=show_copy_button) show_copy_button=show_copy_button)
if buttons is not None:
d.bb.setStandardButtons(buttons)
return d.exec_() == d.Accepted return d.exec_() == d.Accepted
def info_dialog(parent, title, msg, det_msg='', show=False, def info_dialog(parent, title, msg, det_msg='', show=False,

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil import os, shutil
from functools import partial from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog, QThread, pyqtSignal, QProgressDialog from PyQt4.Qt import QMenu, Qt, QInputDialog
from calibre import isbytestring from calibre import isbytestring
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
@ -16,7 +16,7 @@ from calibre.utils.config import prefs
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog, info_dialog question_dialog, info_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.check_library import CheckLibraryDialog from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck
class LibraryUsageStats(object): # {{{ class LibraryUsageStats(object): # {{{
@ -76,76 +76,6 @@ class LibraryUsageStats(object): # {{{
self.write_stats() self.write_stats()
# }}} # }}}
# Check Integrity {{{
class VacThread(QThread):
check_done = pyqtSignal(object, object)
callback = pyqtSignal(object, object)
def __init__(self, parent, db):
QThread.__init__(self, parent)
self.db = db
self._parent = parent
def run(self):
err = bad = None
try:
bad = self.db.check_integrity(self.callbackf)
except:
import traceback
err = traceback.format_exc()
self.check_done.emit(bad, err)
def callbackf(self, progress, msg):
self.callback.emit(progress, msg)
class CheckIntegrity(QProgressDialog):
def __init__(self, db, parent=None):
QProgressDialog.__init__(self, parent)
self.db = db
self.setCancelButton(None)
self.setMinimum(0)
self.setMaximum(100)
self.setWindowTitle(_('Checking database integrity'))
self.setAutoReset(False)
self.setValue(0)
self.vthread = VacThread(self, db)
self.vthread.check_done.connect(self.check_done,
type=Qt.QueuedConnection)
self.vthread.callback.connect(self.callback, type=Qt.QueuedConnection)
self.vthread.start()
def callback(self, progress, msg):
self.setLabelText(msg)
self.setValue(int(100*progress))
def check_done(self, bad, err):
if err:
error_dialog(self, _('Error'),
_('Failed to check database integrity'),
det_msg=err, show=True)
elif bad:
titles = [self.db.title(x, index_is_id=True) for x in bad]
det_msg = '\n'.join(titles)
warning_dialog(self, _('Some inconsistencies found'),
_('The following books had formats or covers listed in the '
'database that are not actually available. '
'The entries for the formats/covers have been removed. '
'You should check them manually. This can '
'happen if you manipulate the files in the '
'library folder directly.'), det_msg=det_msg, show=True)
else:
info_dialog(self, _('No errors found'),
_('The integrity check completed with no uncorrectable errors found.'),
show=True)
self.reset()
# }}}
class ChooseLibraryAction(InterfaceAction): class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library' name = 'Choose Library'
@ -209,14 +139,6 @@ class ChooseLibraryAction(InterfaceAction):
None, None), attr='action_check_library') None, None), attr='action_check_library')
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection) ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac) self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Check database integrity'), 'lt.png',
None, None), attr='action_check_database')
ac.triggered.connect(self.check_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Recover database'), 'lt.png',
None, None), attr='action_restore_database')
ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
self.choose_menu.addMenu(self.maintenance_menu) self.choose_menu.addMenu(self.maintenance_menu)
def pick_random(self, *args): def pick_random(self, *args):
@ -346,28 +268,39 @@ class ChooseLibraryAction(InterfaceAction):
'rate of approximately 1 book every three seconds.'), show=True) 'rate of approximately 1 book every three seconds.'), show=True)
def check_library(self): def check_library(self):
db = self.gui.library_view.model().db self.gui.library_view.save_state()
d = CheckLibraryDialog(self.gui.parent(), db)
d.exec_()
def check_database(self, *args):
m = self.gui.library_view.model() m = self.gui.library_view.model()
m.stop_metadata_backup() m.stop_metadata_backup()
try: db = m.db
d = CheckIntegrity(m.db, self.gui) db.prefs.disable_setting = True
d.exec_()
finally:
m.start_metadata_backup()
def restore_database(self): d = DBCheck(self.gui, db)
info_dialog(self.gui, _('Recover database'), '<p>'+ d.start()
_( try:
'This command rebuilds your calibre database from the information ' d.conn.close()
'stored by calibre in the OPF files.<p>' except:
'This function is not currently available in the GUI. You can ' pass
'recover your database using the \'calibredb restore_database\' ' d.break_cycles()
'command line function.' self.gui.library_moved(db.library_path, call_close=not
), show=True) d.closed_orig_conn)
if d.rejected:
return
if d.error is None:
if not question_dialog(self.gui, _('Success'),
_('Found no errors in your calibre library database.'
' Do you want calibre to check if the files in your '
' library match the information in the database?')):
return
else:
return error_dialog(self.gui, _('Failed'),
_('Database integrity check failed, click Show details'
' for details.'), show=True, det_msg=d.error[1])
d = CheckLibraryDialog(self.gui, m.db)
if not d.do_exec():
info_dialog(self.gui, _('No problems found'),
_('The files in your library match the information '
'in the database.'), show=True)
def switch_requested(self, location): def switch_requested(self, location):
if not self.change_library_allowed(): if not self.change_library_allowed():

View File

@ -19,7 +19,9 @@ class PluginWidget(QWidget, Ui_Form):
('bib_entry', 0), #mixed ('bib_entry', 0), #mixed
('bibfile_enc', 0), #utf-8 ('bibfile_enc', 0), #utf-8
('bibfile_enctag', 0), #strict ('bibfile_enctag', 0), #strict
('impcit', True) ] ('impcit', True),
('addfiles', False),
]
sync_enabled = False sync_enabled = False
formats = set(['bib']) formats = set(['bib'])
@ -49,7 +51,7 @@ class PluginWidget(QWidget, Ui_Form):
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']: if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
getattr(self, opt[0]).setCurrentIndex(opt_value) getattr(self, opt[0]).setCurrentIndex(opt_value)
elif opt[0] == 'impcit' : elif opt[0] in ['impcit', 'addfiles'] :
getattr(self, opt[0]).setChecked(opt_value) getattr(self, opt[0]).setChecked(opt_value)
else: else:
getattr(self, opt[0]).setText(opt_value) getattr(self, opt[0]).setText(opt_value)
@ -76,7 +78,7 @@ class PluginWidget(QWidget, Ui_Form):
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']: if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
opt_value = getattr(self,opt[0]).currentIndex() opt_value = getattr(self,opt[0]).currentIndex()
elif opt[0] == 'impcit' : elif opt[0] in ['impcit', 'addfiles'] :
opt_value = getattr(self, opt[0]).isChecked() opt_value = getattr(self, opt[0]).isChecked()
else : else :
opt_value = unicode(getattr(self, opt[0]).text()) opt_value = unicode(getattr(self, opt[0]).text())

View File

@ -47,7 +47,7 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="1" rowspan="12"> <item row="1" column="1" rowspan="11">
<widget class="QListWidget" name="db_fields"> <widget class="QListWidget" name="db_fields">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding"> <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
@ -141,6 +141,13 @@
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="8" column="0">
<widget class="QCheckBox" name="addfiles">
<property name="text">
<string>Add files path with formats?</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Expression to form the BibTeX citation tag:</string> <string>Expression to form the BibTeX citation tag:</string>

View File

@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys
from threading import Thread from threading import Thread
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
Qt, pyqtSignal, QDialog, QMessageBox Qt, pyqtSignal, QDialog
from calibre.customize.ui import available_input_formats, available_output_formats, \ from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins device_plugins
@ -609,10 +609,8 @@ class DeviceMixin(object): # {{{
autos = u'\n'.join(map(unicode, map(force_unicode, autos))) autos = u'\n'.join(map(unicode, map(force_unicode, autos)))
return self.ask_a_yes_no_question( return self.ask_a_yes_no_question(
_('No suitable formats'), msg, _('No suitable formats'), msg,
buttons=QMessageBox.Yes|QMessageBox.Cancel,
ans_when_user_unavailable=True, ans_when_user_unavailable=True,
det_msg=autos, det_msg=autos
show_copy_button=False
) )
def set_default_thumbnail(self, height): def set_default_thumbnail(self, height):

View File

@ -3,16 +3,132 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__license__ = 'GPL v3' __license__ = 'GPL v3'
import os import os, shutil
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \ from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \ QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
QLineEdit, Qt QLineEdit, Qt, QProgressBar, QSize, QTimer
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.library.check_library import CheckLibrary, CHECKS from calibre.library.check_library import CheckLibrary, CHECKS
from calibre.library.database2 import delete_file, delete_tree from calibre.library.database2 import delete_file, delete_tree
from calibre import prints from calibre import prints, as_unicode
from calibre.ptempfile import PersistentTemporaryFile
from calibre.library.sqlite import DBThread, OperationalError
class DBCheck(QDialog):
def __init__(self, parent, db):
QDialog.__init__(self, parent)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.l1 = QLabel(_('Checking database integrity')+'...')
self.setWindowTitle(_('Checking database integrity'))
self.l.addWidget(self.l1)
self.pb = QProgressBar(self)
self.l.addWidget(self.pb)
self.pb.setMaximum(0)
self.pb.setMinimum(0)
self.msg = QLabel('')
self.l.addWidget(self.msg)
self.msg.setWordWrap(True)
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
self.l.addWidget(self.bb)
self.bb.rejected.connect(self.reject)
self.resize(self.sizeHint() + QSize(100, 50))
self.error = None
self.db = db
self.closed_orig_conn = False
def start(self):
self.user_version = self.db.user_version
self.rejected = False
self.db.clean()
self.db.conn.close()
self.closed_orig_conn = True
t = DBThread(self.db.dbpath, False)
t.connect()
self.conn = t.conn
self.dump = self.conn.iterdump()
self.statements = []
self.count = 0
self.msg.setText(_('Dumping database to SQL'))
# Give the backup thread time to stop
QTimer.singleShot(2000, self.do_one_dump)
self.exec_()
def do_one_dump(self):
if self.rejected:
return
try:
try:
self.statements.append(self.dump.next())
self.count += 1
except StopIteration:
self.start_load()
return
QTimer.singleShot(0, self.do_one_dump)
except Exception, e:
import traceback
self.error = (as_unicode(e), traceback.format_exc())
self.reject()
def start_load(self):
self.conn.close()
self.pb.setMaximum(self.count)
self.pb.setValue(0)
self.msg.setText(_('Loading database from SQL'))
self.db.conn.close()
self.ndbpath = PersistentTemporaryFile('.db')
self.ndbpath.close()
self.ndbpath = self.ndbpath.name
t = DBThread(self.ndbpath, False)
t.connect()
self.conn = t.conn
self.conn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
self.conn.commit()
QTimer.singleShot(0, self.do_one_load)
def do_one_load(self):
if self.rejected:
return
if self.count > 0:
try:
try:
self.conn.execute(self.statements.pop(0))
except OperationalError:
if self.count > 1:
# The last statement in the dump could be an extra
# commit, so ignore it.
raise
self.pb.setValue(self.pb.value() + 1)
self.count -= 1
QTimer.singleShot(0, self.do_one_load)
except Exception, e:
import traceback
self.error = (as_unicode(e), traceback.format_exc())
self.reject()
else:
self.replace_db()
def replace_db(self):
self.conn.commit()
self.conn.execute('pragma user_version=%d'%int(self.user_version))
self.conn.commit()
self.conn.close()
shutil.copyfile(self.ndbpath, self.db.dbpath)
self.db = None
self.accept()
def break_cycles(self):
self.statements = self.unpickler = self.db = self.conn = None
def reject(self):
self.rejected = True
QDialog.reject(self)
class Item(QTreeWidgetItem): class Item(QTreeWidgetItem):
pass pass
@ -23,7 +139,7 @@ class CheckLibraryDialog(QDialog):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.db = db self.db = db
self.setWindowTitle(_('Check Library')) self.setWindowTitle(_('Check Library -- Problems Found'))
self._layout = QVBoxLayout(self) self._layout = QVBoxLayout(self)
self.setLayout(self._layout) self.setLayout(self._layout)
@ -32,7 +148,7 @@ class CheckLibraryDialog(QDialog):
self.log.itemChanged.connect(self.item_changed) self.log.itemChanged.connect(self.item_changed)
self._layout.addWidget(self.log) self._layout.addWidget(self.log)
self.check_button = QPushButton(_('&Run the check')) self.check_button = QPushButton(_('&Run the check again'))
self.check_button.setDefault(False) self.check_button.setDefault(False)
self.check_button.clicked.connect(self.run_the_check) self.check_button.clicked.connect(self.run_the_check)
self.copy_button = QPushButton(_('Copy &to clipboard')) self.copy_button = QPushButton(_('Copy &to clipboard'))
@ -80,8 +196,17 @@ class CheckLibraryDialog(QDialog):
self.resize(750, 500) self.resize(750, 500)
self.bbox.setEnabled(True) self.bbox.setEnabled(True)
def do_exec(self):
self.run_the_check() self.run_the_check()
probs = 0
for c in self.problem_count:
probs += self.problem_count[c]
if probs == 0:
return False
self.exec_()
return True
def accept(self): def accept(self):
self.db.prefs['check_library_ignore_extensions'] = \ self.db.prefs['check_library_ignore_extensions'] = \
unicode(self.ext_ignores.text()) unicode(self.ext_ignores.text())
@ -103,7 +228,10 @@ class CheckLibraryDialog(QDialog):
attr, h, checkable, fixable = check attr, h, checkable, fixable = check
list = getattr(checker, attr, None) list = getattr(checker, attr, None)
if list is None: if list is None:
self.problem_count[attr] = 0
return return
else:
self.problem_count[attr] = len(list)
tl = Item() tl = Item()
tl.setText(0, h) tl.setText(0, h)
@ -134,6 +262,7 @@ class CheckLibraryDialog(QDialog):
t.setHeaderLabels([_('Name'), _('Path from library')]) t.setHeaderLabels([_('Name'), _('Path from library')])
self.all_items = [] self.all_items = []
self.top_level_items = {} self.top_level_items = {}
self.problem_count = {}
for check in CHECKS: for check in CHECKS:
builder(t, checker, check) builder(t, checker, check)

View File

@ -14,7 +14,7 @@
<string>Active Jobs</string> <string>Active Jobs</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/jobs.png</normaloff>:/images/jobs.png</iconset> <normaloff>:/images/jobs.png</normaloff>:/images/jobs.png</iconset>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
@ -44,16 +44,16 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="details_button"> <widget class="QPushButton" name="kill_button">
<property name="text"> <property name="text">
<string>Show job &amp;details</string> <string>&amp;Stop selected job</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="kill_button"> <widget class="QPushButton" name="details_button">
<property name="text"> <property name="text">
<string>&amp;Stop selected job</string> <string>Show job &amp;details</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -39,6 +39,12 @@ class MessageBox(QDialog, Ui_Dialog):
self.det_msg.setPlainText(det_msg) self.det_msg.setPlainText(det_msg)
self.det_msg.setVisible(False) self.det_msg.setVisible(False)
if show_copy_button:
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
self.bb.ActionRole)
self.ctc_button.clicked.connect(self.copy_to_clipboard)
if det_msg: if det_msg:
self.show_det_msg = _('Show &details') self.show_det_msg = _('Show &details')
self.hide_det_msg = _('Hide &details') self.hide_det_msg = _('Hide &details')
@ -47,11 +53,6 @@ class MessageBox(QDialog, Ui_Dialog):
self.det_msg_toggle.setToolTip( self.det_msg_toggle.setToolTip(
_('Show detailed information about this error')) _('Show detailed information about this error'))
if show_copy_button:
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
self.bb.ActionRole)
self.ctc_button.clicked.connect(self.copy_to_clipboard)
self.copy_action = QAction(self) self.copy_action = QAction(self)
self.addAction(self.copy_action) self.addAction(self.copy_action)
@ -91,7 +92,10 @@ class MessageBox(QDialog, Ui_Dialog):
def showEvent(self, ev): def showEvent(self, ev):
ret = QDialog.showEvent(self, ev) ret = QDialog.showEvent(self, ev)
if self.is_question: if self.is_question:
self.bb.button(self.bb.Yes).setFocus(Qt.OtherFocusReason) try:
self.bb.button(self.bb.Yes).setFocus(Qt.OtherFocusReason)
except:
pass# Buttons were changed
else: else:
self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason) self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason)
return ret return ret

View File

@ -7,7 +7,7 @@ import re, os
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \ pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \
QMessageBox, QDate QDate
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
@ -15,7 +15,8 @@ from calibre.ebooks.metadata import string_to_authors, authors_to_string
from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ebooks.metadata.book.base import composite_formatter
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, gprefs from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \
gprefs, question_dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.config import dynamic, JSONConfig from calibre.utils.config import dynamic, JSONConfig
from calibre.utils.titlecase import titlecase from calibre.utils.titlecase import titlecase
@ -888,12 +889,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if self.query_field.currentIndex() == 0: if self.query_field.currentIndex() == 0:
return return
ret = QMessageBox.question(self, _("Delete saved search/replace"), if not question_dialog(self, _("Delete saved search/replace"),
_("The selected saved search/replace will be deleted. " _("The selected saved search/replace will be deleted. "
"Are you sure?"), "Are you sure?")):
QMessageBox.Ok, QMessageBox.Cancel)
if ret == QMessageBox.Cancel:
return return
item_id = self.query_field.currentIndex() item_id = self.query_field.currentIndex()
@ -917,11 +915,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
new = True new = True
name = unicode(name) name = unicode(name)
if name in self.queries.keys(): if name in self.queries.keys():
ret = QMessageBox.question(self, _("Save search/replace"), if not question_dialog(self, _("Save search/replace"),
_("That saved search/replace already exists and will be overwritten. " _("That saved search/replace already exists and will be overwritten. "
"Are you sure?"), "Are you sure?")):
QMessageBox.Ok, QMessageBox.Cancel)
if ret == QMessageBox.Cancel:
return return
new = False new = False

View File

@ -11,7 +11,7 @@ from functools import partial
from threading import Thread from threading import Thread
from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \ from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \
QPixmap, QListWidgetItem, QDialog, pyqtSignal, QMessageBox, QIcon, \ QPixmap, QListWidgetItem, QDialog, pyqtSignal, QIcon, \
QPushButton QPushButton
from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \ from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \
@ -208,6 +208,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
from calibre.gui2 import config from calibre.gui2 import config
title = unicode(self.title.text()).strip() title = unicode(self.title.text()).strip()
author = unicode(self.authors.text()).strip() author = unicode(self.authors.text()).strip()
if author.endswith('&'):
author = author[:-1].strip()
if not title or not author: if not title or not author:
return error_dialog(self, _('Specify title and author'), return error_dialog(self, _('Specify title and author'),
_('You must specify a title and author before generating ' _('You must specify a title and author before generating '
@ -768,9 +770,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if question_dialog(self, _('Tags changed'), if question_dialog(self, _('Tags changed'),
_('You have changed the tags. In order to use the tags' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' editor, you must either discard or apply these '
'changes'), show_copy_button=False, 'changes. Apply changes?'), show_copy_button=False):
buttons=QMessageBox.Apply|QMessageBox.Discard,
yes_button=QMessageBox.Apply):
self.apply_tags(commit=True, notify=True) self.apply_tags(commit=True, notify=True)
self.original_tags = unicode(self.tags.text()) self.original_tags = unicode(self.tags.text())
else: else:

View File

@ -259,14 +259,14 @@ class Scheduler(QObject):
if self.oldest > 0: if self.oldest > 0:
delta = timedelta(days=self.oldest) delta = timedelta(days=self.oldest)
try: try:
ids = self.recipe_model.db.tags_older_than(_('News'), delta) ids = list(self.recipe_model.db.tags_older_than(_('News'),
delta))
except: except:
# Should never happen # Should never happen
ids = [] ids = []
import traceback import traceback
traceback.print_exc() traceback.print_exc()
if ids: if ids:
ids = list(ids)
if ids: if ids:
self.delete_old_news.emit(ids) self.delete_old_news.emit(ids)
QTimer.singleShot(60 * 60 * 1000, self.oldest_check) QTimer.singleShot(60 * 60 * 1000, self.oldest_check)

View File

@ -425,7 +425,6 @@ class JobsDialog(QDialog, Ui_JobsDialog):
for index in self.jobs_view.selectedIndexes(): for index in self.jobs_view.selectedIndexes():
row = index.row() row = index.row()
self.model.kill_job(row, self) self.model.kill_job(row, self)
return
def kill_all_jobs(self, *args): def kill_all_jobs(self, *args):
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop all non-device jobs?')): if question_dialog(self, _('Are you sure?'), _('Do you really want to stop all non-device jobs?')):

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, time, socket, traceback import sys, os, time, socket, traceback
from functools import partial from functools import partial
from PyQt4.Qt import QCoreApplication, QIcon, QMessageBox, QObject, QTimer, \ from PyQt4.Qt import QCoreApplication, QIcon, QObject, QTimer, \
QThread, pyqtSignal, Qt, QProgressDialog, QString, QPixmap, \ QThread, pyqtSignal, Qt, QProgressDialog, QString, QPixmap, \
QSplashScreen, QApplication QSplashScreen, QApplication
@ -319,9 +319,6 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None):
def cant_start(msg=_('If you are sure it is not running')+', ', def cant_start(msg=_('If you are sure it is not running')+', ',
what=None): what=None):
d = QMessageBox(QMessageBox.Critical, _('Cannot Start ')+__appname__,
'<p>'+(_('%s is already running.')%__appname__)+'</p>',
QMessageBox.Ok)
base = '<p>%s</p><p>%s %s' base = '<p>%s</p><p>%s %s'
where = __appname__ + ' '+_('may be running in the system tray, in the')+' ' where = __appname__ + ' '+_('may be running in the system tray, in the')+' '
if isosx: if isosx:
@ -334,8 +331,10 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
else: else:
what = _('try deleting the file')+': '+ADDRESS what = _('try deleting the file')+': '+ADDRESS
d.setInformativeText(base%(where, msg, what)) info = base%(where, msg, what)
d.exec_() error_dialog(None, _('Cannot Start ')+__appname__,
'<p>'+(_('%s is already running.')%__appname__)+'</p>'+info, show=True)
raise SystemExit(1) raise SystemExit(1)
def communicate(args): def communicate(args):

View File

@ -87,7 +87,7 @@ class MainWindow(QMainWindow):
fe = sio.getvalue() fe = sio.getvalue()
prints(fe, file=sys.stderr) prints(fe, file=sys.stderr)
msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace') msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace')
error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe, error_dialog(self, _('Unhandled exception'), msg, det_msg=fe,
show=True) show=True)
except BaseException: except BaseException:
pass pass

View File

@ -10,7 +10,7 @@ import textwrap, re, os
from PyQt4.Qt import Qt, QDateEdit, QDate, \ from PyQt4.Qt import Qt, QDateEdit, QDate, \
QIcon, QToolButton, QWidget, QLabel, QGridLayout, \ QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \ QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
QPushButton, QSpinBox, QMessageBox, QLineEdit QPushButton, QSpinBox, QLineEdit
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \ from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
EnComboBox, FormatList, ImageView, CompleteLineEdit EnComboBox, FormatList, ImageView, CompleteLineEdit
@ -848,9 +848,7 @@ class TagsEdit(CompleteLineEdit): # {{{
if question_dialog(self, _('Tags changed'), if question_dialog(self, _('Tags changed'),
_('You have changed the tags. In order to use the tags' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' editor, you must either discard or apply these '
'changes'), show_copy_button=False, 'changes. Apply changes?'), show_copy_button=False):
buttons=QMessageBox.Apply|QMessageBox.Discard,
yes_button=QMessageBox.Apply):
self.commit(db, id_) self.commit(db, id_)
db.commit() db.commit()
self.original_val = self.current_val self.original_val = self.current_val

View File

@ -188,6 +188,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.plugin_view.PositionAtCenter) self.plugin_view.PositionAtCenter)
self.plugin_view.scrollTo(idx, self.plugin_view.scrollTo(idx,
self.plugin_view.PositionAtCenter) self.plugin_view.PositionAtCenter)
self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx)
else: else:
error_dialog(self, _('No valid plugin path'), error_dialog(self, _('No valid plugin path'),
_('%s is not a valid plugin path')%path).exec_() _('%s is not a valid plugin path')%path).exec_()
@ -220,10 +223,16 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
_('Plugin: %s does not need customization')%plugin.name).exec_() _('Plugin: %s does not need customization')%plugin.name).exec_()
return return
self.changed_signal.emit() self.changed_signal.emit()
from calibre.customize import InterfaceActionBase
if isinstance(plugin, InterfaceActionBase) and not getattr(plugin,
'actual_iaction_plugin_loaded', False):
return error_dialog(self, _('Must restart'),
_('You must restart calibre before you can'
' configure the <b>%s</b> plugin')%plugin.name, show=True)
if plugin.do_user_config(): if plugin.do_user_config():
self._plugin_model.refresh_plugin(plugin) self._plugin_model.refresh_plugin(plugin)
elif op == 'remove': elif op == 'remove':
msg = _('Plugin {0} successfully removed').format(plugin.name) msg = _('Plugin <b>{0}</b> successfully removed').format(plugin.name)
if remove_plugin(plugin): if remove_plugin(plugin):
self._plugin_model.populate() self._plugin_model.populate()
self._plugin_model.reset() self._plugin_model.reset()

View File

@ -12,11 +12,9 @@ __docformat__ = 'restructuredtext en'
import collections, os, sys, textwrap, time import collections, os, sys, textwrap, time
from Queue import Queue, Empty from Queue import Queue, Empty
from threading import Thread from threading import Thread
from PyQt4.Qt import Qt, SIGNAL, QTimer, \ from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \
QPixmap, QMenu, QIcon, pyqtSignal, \ QMenu, QIcon, pyqtSignal, \
QDialog, \ QDialog, QSystemTrayIcon, QApplication, QKeySequence
QSystemTrayIcon, QApplication, QKeySequence, \
QMessageBox, QHelpEvent, QAction
from calibre import prints from calibre import prints
from calibre.constants import __appname__, isosx from calibre.constants import __appname__, isosx
@ -101,28 +99,40 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.opts = opts self.opts = opts
self.device_connected = None self.device_connected = None
self.gui_debug = gui_debug self.gui_debug = gui_debug
acmap = OrderedDict() self.iactions = OrderedDict()
for action in interface_actions(): for action in interface_actions():
if opts.ignore_plugins and action.plugin_path is not None: if opts.ignore_plugins and action.plugin_path is not None:
continue continue
try: try:
ac = action.load_actual_plugin(self) ac = self.init_iaction(action)
except: except:
# Ignore errors in loading user supplied plugins # Ignore errors in loading user supplied plugins
import traceback import traceback
traceback.print_exc() traceback.print_exc()
if ac.plugin_path is None: if action.plugin_path is None:
raise raise
continue
ac.plugin_path = action.plugin_path ac.plugin_path = action.plugin_path
ac.interface_action_base_plugin = action ac.interface_action_base_plugin = action
if ac.name in acmap:
if ac.priority >= acmap[ac.name].priority:
acmap[ac.name] = ac
else:
acmap[ac.name] = ac
self.iactions = acmap self.add_iaction(ac)
def init_iaction(self, action):
ac = action.load_actual_plugin(self)
ac.plugin_path = action.plugin_path
ac.interface_action_base_plugin = action
action.actual_iaction_plugin_loaded = True
return ac
def add_iaction(self, ac):
acmap = self.iactions
if ac.name in acmap:
if ac.priority >= acmap[ac.name].priority:
acmap[ac.name] = ac
else:
acmap[ac.name] = ac
def initialize(self, library_path, db, listener, actions, show_gui=True): def initialize(self, library_path, db, listener, actions, show_gui=True):
opts = self.opts opts = self.opts
@ -345,11 +355,12 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def is_minimized_to_tray(self): def is_minimized_to_tray(self):
return getattr(self, '__systray_minimized', False) return getattr(self, '__systray_minimized', False)
def ask_a_yes_no_question(self, title, msg, **kwargs): def ask_a_yes_no_question(self, title, msg, det_msg='',
awu = kwargs.pop('ans_when_user_unavailable', True) show_copy_button=False, ans_when_user_unavailable=True):
if self.is_minimized_to_tray: if self.is_minimized_to_tray:
return awu return ans_when_user_unavailable
return question_dialog(self, title, msg, **kwargs) return question_dialog(self, title, msg, det_msg=det_msg,
show_copy_button=show_copy_button)
def hide_windows(self): def hide_windows(self):
for window in QApplication.topLevelWidgets(): for window in QApplication.topLevelWidgets():
@ -408,7 +419,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def booklists(self): def booklists(self):
return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
def library_moved(self, newloc, copy_structure=False): def library_moved(self, newloc, copy_structure=False, call_close=True):
if newloc is None: return if newloc is None: return
default_prefs = None default_prefs = None
try: try:
@ -441,7 +452,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.apply_named_search_restriction(db.prefs['gui_restriction']) self.apply_named_search_restriction(db.prefs['gui_restriction'])
if olddb is not None: if olddb is not None:
try: try:
olddb.conn.close() if call_close:
olddb.conn.close()
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -588,11 +600,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
Quitting may cause corruption on the device.<br> Quitting may cause corruption on the device.<br>
Are you sure you want to quit?''')+'</p>' Are you sure you want to quit?''')+'</p>'
d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg, if not question_dialog(self, _('Active jobs'), msg):
QMessageBox.Yes|QMessageBox.No, self)
d.setIconPixmap(QPixmap(I('dialog_warning.png')))
d.setDefaultButton(QMessageBox.No)
if d.exec_() != QMessageBox.Yes:
return False return False
return True return True

View File

@ -24,10 +24,9 @@ from calibre.utils.logging import default_log as log
from calibre.utils.zipfile import ZipFile, ZipInfo from calibre.utils.zipfile import ZipFile, ZipInfo
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
FIELDS = ['all', 'author_sort', 'authors', 'comments', FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments',
'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating', 'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid']
'uuid']
#Allowed fields for template #Allowed fields for template
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
@ -54,8 +53,10 @@ class CSV_XML(CatalogPlugin): # {{{
'database. Should be a comma-separated list of fields.\n' 'database. Should be a comma-separated list of fields.\n'
'Available fields: %s,\n' 'Available fields: %s,\n'
'plus user-created custom fields.\n' 'plus user-created custom fields.\n'
'Example: %s=title,authors,tags\n'
"Default: '%%default'\n" "Default: '%%default'\n"
"Applies to: CSV, XML output formats")%', '.join(FIELDS)), "Applies to: CSV, XML output formats")%(', '.join(FIELDS),
'--fields')),
Option('--sort-by', Option('--sort-by',
default = 'id', default = 'id',
@ -231,8 +232,10 @@ class BIBTEX(CatalogPlugin): # {{{
help = _('The fields to output when cataloging books in the ' help = _('The fields to output when cataloging books in the '
'database. Should be a comma-separated list of fields.\n' 'database. Should be a comma-separated list of fields.\n'
'Available fields: %s.\n' 'Available fields: %s.\n'
'Example: %s=title,authors,tags\n'
"Default: '%%default'\n" "Default: '%%default'\n"
"Applies to: BIBTEX output format")%', '.join(FIELDS)), "Applies to: BIBTEX output format")%(', '.join(FIELDS),
'--fields')),
Option('--sort-by', Option('--sort-by',
default = 'id', default = 'id',
@ -252,6 +255,15 @@ class BIBTEX(CatalogPlugin): # {{{
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: BIBTEX output format")), "Applies to: BIBTEX output format")),
Option('--add-files-path',
default = 'True',
dest = 'addfiles',
action = None,
help = _('Create a file entry if formats is selected for BibTeX entries.\n'
'Boolean value: True, False\n'
"Default: '%default'\n"
"Applies to: BIBTEX output format")),
Option('--citation-template', Option('--citation-template',
default = '{authors}{id}', default = '{authors}{id}',
dest = 'bib_cit', dest = 'bib_cit',
@ -298,7 +310,7 @@ class BIBTEX(CatalogPlugin): # {{{
from calibre.utils.bibtex import BibTeX from calibre.utils.bibtex import BibTeX
def create_bibtex_entry(entry, fields, mode, template_citation, def create_bibtex_entry(entry, fields, mode, template_citation,
bibtexdict, citation_bibtex = True): bibtexdict, citation_bibtex=True, calibre_files=True):
#Bibtex doesn't like UTF-8 but keep unicode until writing #Bibtex doesn't like UTF-8 but keep unicode until writing
#Define starting chain or if book valid strict and not book return a Fail string #Define starting chain or if book valid strict and not book return a Fail string
@ -360,8 +372,13 @@ class BIBTEX(CatalogPlugin): # {{{
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item)) bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
elif field == 'formats' : elif field == 'formats' :
item = u', '.join([format.rpartition('.')[2].lower() for format in item]) #Add file path if format is selected
bibtex_entry.append(u'formats = "%s"' % item) formats = [format.rpartition('.')[2].lower() for format in item]
bibtex_entry.append(u'formats = "%s"' % u', '.join(formats))
if calibre_files:
files = [u':%s:%s' % (format, format.rpartition('.')[2].upper())\
for format in item]
bibtex_entry.append(u'files = "%s"' % u', '.join(files))
elif field == 'series_index' : elif field == 'series_index' :
bibtex_entry.append(u'volume = "%s"' % int(item)) bibtex_entry.append(u'volume = "%s"' % int(item))
@ -511,31 +528,40 @@ class BIBTEX(CatalogPlugin): # {{{
else : else :
citation_bibtex= opts.impcit citation_bibtex= opts.impcit
#Check add file entry and go to default in case of bad CLI
if isinstance(opts.addfiles, (StringType, UnicodeType)) :
if opts.addfiles == 'False' :
addfiles_bibtex = False
elif opts.addfiles == 'True' :
addfiles_bibtex = True
else :
log(" WARNING: incorrect --add-files-path, revert to default")
addfiles_bibtex= True
else :
addfiles_bibtex = opts.addfiles
#Preprocess for error and light correction #Preprocess for error and light correction
template_citation = preprocess_template(opts.bib_cit) template_citation = preprocess_template(opts.bib_cit)
#Open output and write entries #Open output and write entries
outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag) with codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)\
as outfile:
#File header
nb_entries = len(data)
#check in book strict if all is ok else throw a warning into log
if bib_entry == 'book' :
nb_books = len(filter(check_entry_book_valid, data))
if nb_books < nb_entries :
log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
nb_entries = nb_books
#File header outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
nb_entries = len(data) outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
#check in book strict if all is ok else throw a warning into log for entry in data:
if bib_entry == 'book' : outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
nb_books = len(filter(check_entry_book_valid, data)) bibtexc, citation_bibtex, addfiles_bibtex))
if nb_books < nb_entries :
log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
nb_entries = nb_books
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
for entry in data:
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
bibtexc, citation_bibtex))
outfile.close()
# }}} # }}}
class EPUB_MOBI(CatalogPlugin): class EPUB_MOBI(CatalogPlugin):

View File

@ -21,7 +21,7 @@ from calibre.library.field_metadata import FieldMetadata, TagsIcons
from calibre.library.schema_upgrades import SchemaUpgrade from calibre.library.schema_upgrades import SchemaUpgrade
from calibre.library.caches import ResultCache from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread from calibre.library.sqlite import connect, IntegrityError
from calibre.library.prefs import DBPrefs from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string from calibre.ebooks.metadata import string_to_authors, authors_to_string
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
@ -2796,82 +2796,3 @@ books_series_link feeds
yield id, title, script yield id, title, script
def check_integrity(self, callback):
callback(0., _('Checking SQL integrity...'))
self.clean()
user_version = self.user_version
sql = '\n'.join(self.conn.dump())
self.conn.close()
dest = self.dbpath+'.tmp'
if os.path.exists(dest):
os.remove(dest)
conn = None
try:
ndb = DBThread(dest, None)
ndb.connect()
conn = ndb.conn
conn.execute('create table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
conn.commit()
conn.executescript(sql)
conn.commit()
conn.execute('pragma user_version=%d'%user_version)
conn.commit()
conn.execute('drop table temp_sequence')
conn.commit()
conn.close()
except:
if conn is not None:
try:
conn.close()
except:
pass
if os.path.exists(dest):
os.remove(dest)
raise
else:
shutil.copyfile(dest, self.dbpath)
self.connect()
self.initialize_dynamic()
self.refresh()
if os.path.exists(dest):
os.remove(dest)
callback(0.1, _('Checking for missing files.'))
bad = {}
us = self.data.universal_set()
total = float(len(us))
for i, id in enumerate(us):
formats = self.data.get(id, self.FIELD_MAP['formats'], row_is_id=True)
if not formats:
formats = []
else:
formats = [x.lower() for x in formats.split(',')]
actual_formats = self.formats(id, index_is_id=True)
if not actual_formats:
actual_formats = []
else:
actual_formats = [x.lower() for x in actual_formats.split(',')]
for fmt in formats:
if fmt in actual_formats:
continue
if id not in bad:
bad[id] = []
bad[id].append(fmt)
has_cover = self.data.get(id, self.FIELD_MAP['cover'],
row_is_id=True)
if has_cover and self.cover(id, index_is_id=True, as_path=True) is None:
if id not in bad:
bad[id] = []
bad[id].append('COVER')
callback(0.1+0.9*(1+i)/total, _('Checked id') + ' %d'%id)
for id in bad:
for fmt in bad[id]:
if fmt != 'COVER':
self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, fmt.upper()))
else:
self.conn.execute('UPDATE books SET has_cover=0 WHERE id=?', (id,))
self.conn.commit()
self.refresh_ids(list(bad.keys()))
return bad

View File

@ -17,6 +17,7 @@ class DBPrefs(dict):
dict.__init__(self) dict.__init__(self)
self.db = db self.db = db
self.defaults = {} self.defaults = {}
self.disable_setting = False
for key, val in self.db.conn.get('SELECT key,val FROM preferences'): for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
try: try:
val = self.raw_to_object(val) val = self.raw_to_object(val)
@ -45,6 +46,8 @@ class DBPrefs(dict):
self.db.conn.commit() self.db.conn.commit()
def __setitem__(self, key, val): def __setitem__(self, key, val):
if self.disable_setting:
return
raw = self.to_raw(val) raw = self.to_raw(val)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,)) self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key, self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,

View File

@ -107,6 +107,7 @@ My device is not being detected by |app|?
Follow these steps to find the problem: Follow these steps to find the problem:
* Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time. * Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time.
* If you are connecting an Apple iDevice (iPad, iPod Touch, iPhone), use the 'Connect to iTunes' method in the 'Getting started' instructions in `Calibre + Apple iDevices: Start here <http://www.mobileread.com/forums/showthread.php?t=118559>`_.
* Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_. * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_.
* Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is. * Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is.
* In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled. * In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled.
@ -224,13 +225,12 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|.
You wills ee a list of books in Safari, just click on the epub link for whichever book you want to read, Safari will then prompt you to open it with iBooks. You wills ee a list of books in Safari, just click on the epub link for whichever book you want to read, Safari will then prompt you to open it with iBooks.
With the USB cable With the USB cable + iTunes
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
As of |app| version 0.7.0, you can plug your iDevice into the computer using its charging cable, and |app| will detect it and show you a list of books on the device. You can then use the *Send to device button* to send books directly to iBooks on the device. Note that you must have at least iOS 4 installed on your iPhone/iTouch for this to work. Use the 'Connect to iTunes' method in the 'Getting started' instructions in `Calibre + Apple iDevices: Start here <http://www.mobileread.com/forums/showthread.php?t=118559>`_.
This method only works on Windows XP and higher and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported. This method only works on Windows XP and higher, and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported.
For more details on how this works, see `this forum post <http://www.mobileread.com/forums/showpost.php?p=944079&postcount=1>`_.
How do I use |app| with my Android phone? How do I use |app| with my Android phone?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

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

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