mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
b435e375e6
129
Changelog.yaml
129
Changelog.yaml
@ -4,12 +4,137 @@
|
||||
# for important features/bug fixes.
|
||||
# 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
|
||||
date: 2011-01-21
|
||||
|
||||
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: "Conversion: The preprocess html option has now become a new 'Heuristic Processing' option which allows you to control exactly which heuristics are used"
|
||||
|
@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe):
|
||||
|
||||
language = 'en_CA'
|
||||
__author__ = 'Nick Redding'
|
||||
encoding = 'latin1'
|
||||
no_stylesheets = True
|
||||
timefmt = ' [%b %d]'
|
||||
extra_css = '''
|
||||
@ -97,7 +98,9 @@ class CanWestPaper(BasicNewsRecipe):
|
||||
atag = h1tag.find('a',href=True)
|
||||
if not atag:
|
||||
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("url %s" % url)
|
||||
title = self.tag_to_string(atag,False)
|
||||
|
11
resources/recipes/capes_n_babes.recipe
Normal file
11
resources/recipes/capes_n_babes.recipe
Normal 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/')]
|
@ -26,7 +26,7 @@ class BBC(BasicNewsRecipe):
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
feeds = [
|
||||
('Interviews', 'http://www.avclub.com/feed/interview/'),
|
||||
('TV', 'http://www.avclub.com/feed/tv/'),
|
||||
('AV Club Daily', 'http://www.avclub.com/feed/daily'),
|
||||
('Film', 'http://www.avclub.com/feed/film/'),
|
||||
('Music', 'http://www.avclub.com/feed/music/'),
|
||||
|
15
resources/recipes/spin_magazine.recipe
Normal file
15
resources/recipes/spin_magazine.recipe
Normal 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')
|
||||
|
||||
]
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.42'
|
||||
__version__ = '0.7.43'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re
|
||||
|
@ -19,7 +19,8 @@ class ANDROID(USBMS):
|
||||
|
||||
VENDOR_ID = {
|
||||
# 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],
|
||||
0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},
|
||||
|
||||
|
@ -141,8 +141,8 @@ class CoverManager(object):
|
||||
if width is None or height is None:
|
||||
self.log.warning('Failed to read cover dimensions')
|
||||
width, height = 600, 800
|
||||
if self.preserve_aspect_ratio:
|
||||
width, height = 600, 800
|
||||
#if self.preserve_aspect_ratio:
|
||||
# width, height = 600, 800
|
||||
self.svg_template = self.svg_template.replace('__viewbox__',
|
||||
'0 0 %d %d'%(width, height))
|
||||
self.svg_template = self.svg_template.replace('__width__',
|
||||
|
@ -47,7 +47,7 @@ class PDFOutput(OutputFormatPlugin):
|
||||
OptionRecommendation(name='preserve_cover_aspect_ratio',
|
||||
recommended_value=False,
|
||||
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.')
|
||||
),
|
||||
])
|
||||
|
@ -197,14 +197,10 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False,
|
||||
buttons=None, yes_button=None):
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False):
|
||||
from calibre.gui2.dialogs.message_box import MessageBox
|
||||
d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
|
||||
show_copy_button=show_copy_button)
|
||||
if buttons is not None:
|
||||
d.bb.setStandardButtons(buttons)
|
||||
|
||||
return d.exec_() == d.Accepted
|
||||
|
||||
def info_dialog(parent, title, msg, det_msg='', show=False,
|
||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import os, shutil
|
||||
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.constants import filesystem_encoding
|
||||
@ -16,7 +16,7 @@ from calibre.utils.config import prefs
|
||||
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
|
||||
question_dialog, info_dialog
|
||||
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): # {{{
|
||||
|
||||
@ -76,76 +76,6 @@ class LibraryUsageStats(object): # {{{
|
||||
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):
|
||||
|
||||
name = 'Choose Library'
|
||||
@ -209,14 +139,6 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
None, None), attr='action_check_library')
|
||||
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
|
||||
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)
|
||||
|
||||
def pick_random(self, *args):
|
||||
@ -346,28 +268,39 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
'rate of approximately 1 book every three seconds.'), show=True)
|
||||
|
||||
def check_library(self):
|
||||
db = self.gui.library_view.model().db
|
||||
d = CheckLibraryDialog(self.gui.parent(), db)
|
||||
d.exec_()
|
||||
|
||||
def check_database(self, *args):
|
||||
self.gui.library_view.save_state()
|
||||
m = self.gui.library_view.model()
|
||||
m.stop_metadata_backup()
|
||||
try:
|
||||
d = CheckIntegrity(m.db, self.gui)
|
||||
d.exec_()
|
||||
finally:
|
||||
m.start_metadata_backup()
|
||||
db = m.db
|
||||
db.prefs.disable_setting = True
|
||||
|
||||
def restore_database(self):
|
||||
info_dialog(self.gui, _('Recover database'), '<p>'+
|
||||
_(
|
||||
'This command rebuilds your calibre database from the information '
|
||||
'stored by calibre in the OPF files.<p>'
|
||||
'This function is not currently available in the GUI. You can '
|
||||
'recover your database using the \'calibredb restore_database\' '
|
||||
'command line function.'
|
||||
), show=True)
|
||||
d = DBCheck(self.gui, db)
|
||||
d.start()
|
||||
try:
|
||||
d.conn.close()
|
||||
except:
|
||||
pass
|
||||
d.break_cycles()
|
||||
self.gui.library_moved(db.library_path, call_close=not
|
||||
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):
|
||||
if not self.change_library_allowed():
|
||||
|
@ -19,7 +19,9 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
('bib_entry', 0), #mixed
|
||||
('bibfile_enc', 0), #utf-8
|
||||
('bibfile_enctag', 0), #strict
|
||||
('impcit', True) ]
|
||||
('impcit', True),
|
||||
('addfiles', False),
|
||||
]
|
||||
|
||||
sync_enabled = False
|
||||
formats = set(['bib'])
|
||||
@ -49,7 +51,7 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
||||
elif opt[0] == 'impcit' :
|
||||
elif opt[0] in ['impcit', 'addfiles'] :
|
||||
getattr(self, opt[0]).setChecked(opt_value)
|
||||
else:
|
||||
getattr(self, opt[0]).setText(opt_value)
|
||||
@ -76,7 +78,7 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
for opt in self.OPTION_FIELDS:
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
opt_value = getattr(self,opt[0]).currentIndex()
|
||||
elif opt[0] == 'impcit' :
|
||||
elif opt[0] in ['impcit', 'addfiles'] :
|
||||
opt_value = getattr(self, opt[0]).isChecked()
|
||||
else :
|
||||
opt_value = unicode(getattr(self, opt[0]).text())
|
||||
|
@ -47,7 +47,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" rowspan="12">
|
||||
<item row="1" column="1" rowspan="11">
|
||||
<widget class="QListWidget" name="db_fields">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
@ -141,6 +141,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<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">
|
||||
<property name="text">
|
||||
<string>Expression to form the BibTeX citation tag:</string>
|
||||
|
@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys
|
||||
from threading import Thread
|
||||
|
||||
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, \
|
||||
device_plugins
|
||||
@ -609,10 +609,8 @@ class DeviceMixin(object): # {{{
|
||||
autos = u'\n'.join(map(unicode, map(force_unicode, autos)))
|
||||
return self.ask_a_yes_no_question(
|
||||
_('No suitable formats'), msg,
|
||||
buttons=QMessageBox.Yes|QMessageBox.Cancel,
|
||||
ans_when_user_unavailable=True,
|
||||
det_msg=autos,
|
||||
show_copy_button=False
|
||||
det_msg=autos
|
||||
)
|
||||
|
||||
def set_default_thumbnail(self, height):
|
||||
|
@ -3,16 +3,132 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
import os
|
||||
import os, shutil
|
||||
|
||||
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
||||
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
||||
QLineEdit, Qt
|
||||
QLineEdit, Qt, QProgressBar, QSize, QTimer
|
||||
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.library.check_library import CheckLibrary, CHECKS
|
||||
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):
|
||||
pass
|
||||
@ -23,7 +139,7 @@ class CheckLibraryDialog(QDialog):
|
||||
QDialog.__init__(self, parent)
|
||||
self.db = db
|
||||
|
||||
self.setWindowTitle(_('Check Library'))
|
||||
self.setWindowTitle(_('Check Library -- Problems Found'))
|
||||
|
||||
self._layout = QVBoxLayout(self)
|
||||
self.setLayout(self._layout)
|
||||
@ -32,7 +148,7 @@ class CheckLibraryDialog(QDialog):
|
||||
self.log.itemChanged.connect(self.item_changed)
|
||||
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.clicked.connect(self.run_the_check)
|
||||
self.copy_button = QPushButton(_('Copy &to clipboard'))
|
||||
@ -80,8 +196,17 @@ class CheckLibraryDialog(QDialog):
|
||||
self.resize(750, 500)
|
||||
self.bbox.setEnabled(True)
|
||||
|
||||
def do_exec(self):
|
||||
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):
|
||||
self.db.prefs['check_library_ignore_extensions'] = \
|
||||
unicode(self.ext_ignores.text())
|
||||
@ -103,7 +228,10 @@ class CheckLibraryDialog(QDialog):
|
||||
attr, h, checkable, fixable = check
|
||||
list = getattr(checker, attr, None)
|
||||
if list is None:
|
||||
self.problem_count[attr] = 0
|
||||
return
|
||||
else:
|
||||
self.problem_count[attr] = len(list)
|
||||
|
||||
tl = Item()
|
||||
tl.setText(0, h)
|
||||
@ -134,6 +262,7 @@ class CheckLibraryDialog(QDialog):
|
||||
t.setHeaderLabels([_('Name'), _('Path from library')])
|
||||
self.all_items = []
|
||||
self.top_level_items = {}
|
||||
self.problem_count = {}
|
||||
for check in CHECKS:
|
||||
builder(t, checker, check)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Active Jobs</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/jobs.png</normaloff>:/images/jobs.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
@ -44,16 +44,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="details_button">
|
||||
<widget class="QPushButton" name="kill_button">
|
||||
<property name="text">
|
||||
<string>Show job &details</string>
|
||||
<string>&Stop selected job</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="kill_button">
|
||||
<widget class="QPushButton" name="details_button">
|
||||
<property name="text">
|
||||
<string>&Stop selected job</string>
|
||||
<string>Show job &details</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -39,6 +39,12 @@ class MessageBox(QDialog, Ui_Dialog):
|
||||
self.det_msg.setPlainText(det_msg)
|
||||
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:
|
||||
self.show_det_msg = _('Show &details')
|
||||
self.hide_det_msg = _('Hide &details')
|
||||
@ -47,11 +53,6 @@ class MessageBox(QDialog, Ui_Dialog):
|
||||
self.det_msg_toggle.setToolTip(
|
||||
_('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.addAction(self.copy_action)
|
||||
@ -91,7 +92,10 @@ class MessageBox(QDialog, Ui_Dialog):
|
||||
def showEvent(self, ev):
|
||||
ret = QDialog.showEvent(self, ev)
|
||||
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:
|
||||
self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason)
|
||||
return ret
|
||||
|
@ -7,7 +7,7 @@ import re, os
|
||||
|
||||
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
||||
pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \
|
||||
QMessageBox, QDate
|
||||
QDate
|
||||
|
||||
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||
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.meta import get_metadata
|
||||
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.utils.config import dynamic, JSONConfig
|
||||
from calibre.utils.titlecase import titlecase
|
||||
@ -888,12 +889,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
if self.query_field.currentIndex() == 0:
|
||||
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. "
|
||||
"Are you sure?"),
|
||||
QMessageBox.Ok, QMessageBox.Cancel)
|
||||
|
||||
if ret == QMessageBox.Cancel:
|
||||
"Are you sure?")):
|
||||
return
|
||||
|
||||
item_id = self.query_field.currentIndex()
|
||||
@ -917,11 +915,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
new = True
|
||||
name = unicode(name)
|
||||
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. "
|
||||
"Are you sure?"),
|
||||
QMessageBox.Ok, QMessageBox.Cancel)
|
||||
if ret == QMessageBox.Cancel:
|
||||
"Are you sure?")):
|
||||
return
|
||||
new = False
|
||||
|
||||
|
@ -11,7 +11,7 @@ from functools import partial
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \
|
||||
QPixmap, QListWidgetItem, QDialog, pyqtSignal, QMessageBox, QIcon, \
|
||||
QPixmap, QListWidgetItem, QDialog, pyqtSignal, QIcon, \
|
||||
QPushButton
|
||||
|
||||
from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \
|
||||
@ -208,6 +208,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
from calibre.gui2 import config
|
||||
title = unicode(self.title.text()).strip()
|
||||
author = unicode(self.authors.text()).strip()
|
||||
if author.endswith('&'):
|
||||
author = author[:-1].strip()
|
||||
if not title or not author:
|
||||
return error_dialog(self, _('Specify title and author'),
|
||||
_('You must specify a title and author before generating '
|
||||
@ -768,9 +770,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
if question_dialog(self, _('Tags changed'),
|
||||
_('You have changed the tags. In order to use the tags'
|
||||
' editor, you must either discard or apply these '
|
||||
'changes'), show_copy_button=False,
|
||||
buttons=QMessageBox.Apply|QMessageBox.Discard,
|
||||
yes_button=QMessageBox.Apply):
|
||||
'changes. Apply changes?'), show_copy_button=False):
|
||||
self.apply_tags(commit=True, notify=True)
|
||||
self.original_tags = unicode(self.tags.text())
|
||||
else:
|
||||
|
@ -259,14 +259,14 @@ class Scheduler(QObject):
|
||||
if self.oldest > 0:
|
||||
delta = timedelta(days=self.oldest)
|
||||
try:
|
||||
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
|
||||
ids = list(self.recipe_model.db.tags_older_than(_('News'),
|
||||
delta))
|
||||
except:
|
||||
# Should never happen
|
||||
ids = []
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if ids:
|
||||
ids = list(ids)
|
||||
if ids:
|
||||
self.delete_old_news.emit(ids)
|
||||
QTimer.singleShot(60 * 60 * 1000, self.oldest_check)
|
||||
|
@ -425,7 +425,6 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
for index in self.jobs_view.selectedIndexes():
|
||||
row = index.row()
|
||||
self.model.kill_job(row, self)
|
||||
return
|
||||
|
||||
def kill_all_jobs(self, *args):
|
||||
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop all non-device jobs?')):
|
||||
|
@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import sys, os, time, socket, traceback
|
||||
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, \
|
||||
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')+', ',
|
||||
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'
|
||||
where = __appname__ + ' '+_('may be running in the system tray, in the')+' '
|
||||
if isosx:
|
||||
@ -334,8 +331,10 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
|
||||
else:
|
||||
what = _('try deleting the file')+': '+ADDRESS
|
||||
|
||||
d.setInformativeText(base%(where, msg, what))
|
||||
d.exec_()
|
||||
info = base%(where, msg, what)
|
||||
error_dialog(None, _('Cannot Start ')+__appname__,
|
||||
'<p>'+(_('%s is already running.')%__appname__)+'</p>'+info, show=True)
|
||||
|
||||
raise SystemExit(1)
|
||||
|
||||
def communicate(args):
|
||||
|
@ -87,7 +87,7 @@ class MainWindow(QMainWindow):
|
||||
fe = sio.getvalue()
|
||||
prints(fe, file=sys.stderr)
|
||||
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)
|
||||
except BaseException:
|
||||
pass
|
||||
|
@ -10,7 +10,7 @@ import textwrap, re, os
|
||||
from PyQt4.Qt import Qt, QDateEdit, QDate, \
|
||||
QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
|
||||
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
|
||||
QPushButton, QSpinBox, QMessageBox, QLineEdit
|
||||
QPushButton, QSpinBox, QLineEdit
|
||||
|
||||
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
|
||||
EnComboBox, FormatList, ImageView, CompleteLineEdit
|
||||
@ -848,9 +848,7 @@ class TagsEdit(CompleteLineEdit): # {{{
|
||||
if question_dialog(self, _('Tags changed'),
|
||||
_('You have changed the tags. In order to use the tags'
|
||||
' editor, you must either discard or apply these '
|
||||
'changes'), show_copy_button=False,
|
||||
buttons=QMessageBox.Apply|QMessageBox.Discard,
|
||||
yes_button=QMessageBox.Apply):
|
||||
'changes. Apply changes?'), show_copy_button=False):
|
||||
self.commit(db, id_)
|
||||
db.commit()
|
||||
self.original_val = self.current_val
|
||||
|
@ -188,6 +188,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.plugin_view.PositionAtCenter)
|
||||
self.plugin_view.scrollTo(idx,
|
||||
self.plugin_view.PositionAtCenter)
|
||||
self.plugin_view.selectionModel().select(idx,
|
||||
self.plugin_view.selectionModel().ClearAndSelect)
|
||||
self.plugin_view.setCurrentIndex(idx)
|
||||
else:
|
||||
error_dialog(self, _('No valid plugin path'),
|
||||
_('%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_()
|
||||
return
|
||||
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():
|
||||
self._plugin_model.refresh_plugin(plugin)
|
||||
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):
|
||||
self._plugin_model.populate()
|
||||
self._plugin_model.reset()
|
||||
|
@ -12,11 +12,9 @@ __docformat__ = 'restructuredtext en'
|
||||
import collections, os, sys, textwrap, time
|
||||
from Queue import Queue, Empty
|
||||
from threading import Thread
|
||||
from PyQt4.Qt import Qt, SIGNAL, QTimer, \
|
||||
QPixmap, QMenu, QIcon, pyqtSignal, \
|
||||
QDialog, \
|
||||
QSystemTrayIcon, QApplication, QKeySequence, \
|
||||
QMessageBox, QHelpEvent, QAction
|
||||
from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \
|
||||
QMenu, QIcon, pyqtSignal, \
|
||||
QDialog, QSystemTrayIcon, QApplication, QKeySequence
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import __appname__, isosx
|
||||
@ -101,28 +99,40 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.opts = opts
|
||||
self.device_connected = None
|
||||
self.gui_debug = gui_debug
|
||||
acmap = OrderedDict()
|
||||
self.iactions = OrderedDict()
|
||||
for action in interface_actions():
|
||||
if opts.ignore_plugins and action.plugin_path is not None:
|
||||
continue
|
||||
try:
|
||||
ac = action.load_actual_plugin(self)
|
||||
ac = self.init_iaction(action)
|
||||
except:
|
||||
# Ignore errors in loading user supplied plugins
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if ac.plugin_path is None:
|
||||
if action.plugin_path is None:
|
||||
raise
|
||||
continue
|
||||
|
||||
ac.plugin_path = action.plugin_path
|
||||
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):
|
||||
opts = self.opts
|
||||
@ -345,11 +355,12 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
def is_minimized_to_tray(self):
|
||||
return getattr(self, '__systray_minimized', False)
|
||||
|
||||
def ask_a_yes_no_question(self, title, msg, **kwargs):
|
||||
awu = kwargs.pop('ans_when_user_unavailable', True)
|
||||
def ask_a_yes_no_question(self, title, msg, det_msg='',
|
||||
show_copy_button=False, ans_when_user_unavailable=True):
|
||||
if self.is_minimized_to_tray:
|
||||
return awu
|
||||
return question_dialog(self, title, msg, **kwargs)
|
||||
return ans_when_user_unavailable
|
||||
return question_dialog(self, title, msg, det_msg=det_msg,
|
||||
show_copy_button=show_copy_button)
|
||||
|
||||
def hide_windows(self):
|
||||
for window in QApplication.topLevelWidgets():
|
||||
@ -408,7 +419,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
def booklists(self):
|
||||
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
|
||||
default_prefs = None
|
||||
try:
|
||||
@ -441,7 +452,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
||||
if olddb is not None:
|
||||
try:
|
||||
olddb.conn.close()
|
||||
if call_close:
|
||||
olddb.conn.close()
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -588,11 +600,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
Quitting may cause corruption on the device.<br>
|
||||
Are you sure you want to quit?''')+'</p>'
|
||||
|
||||
d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg,
|
||||
QMessageBox.Yes|QMessageBox.No, self)
|
||||
d.setIconPixmap(QPixmap(I('dialog_warning.png')))
|
||||
d.setDefaultButton(QMessageBox.No)
|
||||
if d.exec_() != QMessageBox.Yes:
|
||||
if not question_dialog(self, _('Active jobs'), msg):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -24,10 +24,9 @@ from calibre.utils.logging import default_log as log
|
||||
from calibre.utils.zipfile import ZipFile, ZipInfo
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
|
||||
FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
||||
'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating',
|
||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
||||
'uuid']
|
||||
FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments',
|
||||
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
|
||||
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid']
|
||||
|
||||
#Allowed fields for template
|
||||
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'
|
||||
'Available fields: %s,\n'
|
||||
'plus user-created custom fields.\n'
|
||||
'Example: %s=title,authors,tags\n'
|
||||
"Default: '%%default'\n"
|
||||
"Applies to: CSV, XML output formats")%', '.join(FIELDS)),
|
||||
"Applies to: CSV, XML output formats")%(', '.join(FIELDS),
|
||||
'--fields')),
|
||||
|
||||
Option('--sort-by',
|
||||
default = 'id',
|
||||
@ -231,8 +232,10 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
help = _('The fields to output when cataloging books in the '
|
||||
'database. Should be a comma-separated list of fields.\n'
|
||||
'Available fields: %s.\n'
|
||||
'Example: %s=title,authors,tags\n'
|
||||
"Default: '%%default'\n"
|
||||
"Applies to: BIBTEX output format")%', '.join(FIELDS)),
|
||||
"Applies to: BIBTEX output format")%(', '.join(FIELDS),
|
||||
'--fields')),
|
||||
|
||||
Option('--sort-by',
|
||||
default = 'id',
|
||||
@ -252,6 +255,15 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
"Default: '%default'\n"
|
||||
"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',
|
||||
default = '{authors}{id}',
|
||||
dest = 'bib_cit',
|
||||
@ -298,7 +310,7 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
from calibre.utils.bibtex import BibTeX
|
||||
|
||||
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
|
||||
#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))
|
||||
|
||||
elif field == 'formats' :
|
||||
item = u', '.join([format.rpartition('.')[2].lower() for format in item])
|
||||
bibtex_entry.append(u'formats = "%s"' % item)
|
||||
#Add file path if format is selected
|
||||
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' :
|
||||
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||
@ -511,31 +528,40 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
else :
|
||||
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
|
||||
template_citation = preprocess_template(opts.bib_cit)
|
||||
|
||||
#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
|
||||
nb_entries = len(data)
|
||||
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)))
|
||||
|
||||
#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
|
||||
|
||||
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()
|
||||
for entry in data:
|
||||
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||
bibtexc, citation_bibtex, addfiles_bibtex))
|
||||
# }}}
|
||||
|
||||
class EPUB_MOBI(CatalogPlugin):
|
||||
|
@ -21,7 +21,7 @@ from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||
from calibre.library.schema_upgrades import SchemaUpgrade
|
||||
from calibre.library.caches import ResultCache
|
||||
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.ebooks.metadata import string_to_authors, authors_to_string
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
@ -2796,82 +2796,3 @@ books_series_link feeds
|
||||
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
|
||||
|
@ -17,6 +17,7 @@ class DBPrefs(dict):
|
||||
dict.__init__(self)
|
||||
self.db = db
|
||||
self.defaults = {}
|
||||
self.disable_setting = False
|
||||
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
|
||||
try:
|
||||
val = self.raw_to_object(val)
|
||||
@ -45,6 +46,8 @@ class DBPrefs(dict):
|
||||
self.db.conn.commit()
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
if self.disable_setting:
|
||||
return
|
||||
raw = self.to_raw(val)
|
||||
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
|
||||
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
|
||||
|
@ -107,6 +107,7 @@ My device is not being detected by |app|?
|
||||
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.
|
||||
* 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>`_.
|
||||
* 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.
|
||||
@ -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.
|
||||
|
||||
|
||||
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.
|
||||
For more details on how this works, see `this forum post <http://www.mobileread.com/forums/showpost.php?p=944079&postcount=1>`_.
|
||||
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.
|
||||
|
||||
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
Loading…
x
Reference in New Issue
Block a user