mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates pre 0.7.15
This commit is contained in:
commit
5db52d70af
@ -24,6 +24,7 @@ series_index_auto_increment = 'next'
|
|||||||
# invert: use "fn ln" -> "ln, fn" (the original algorithm)
|
# invert: use "fn ln" -> "ln, fn" (the original algorithm)
|
||||||
# copy : copy author to author_sort without modification
|
# copy : copy author to author_sort without modification
|
||||||
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
|
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
|
||||||
|
# nocomma : "fn ln" -> "ln fn" (without the comma)
|
||||||
author_sort_copy_method = 'invert'
|
author_sort_copy_method = 'invert'
|
||||||
|
|
||||||
|
|
||||||
|
BIN
resources/images/news/futurismic.png
Normal file
BIN
resources/images/news/futurismic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 479 B |
38
resources/recipes/futurismic.recipe
Normal file
38
resources/recipes/futurismic.recipe
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
futurismic.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class Futurismic(BasicNewsRecipe):
|
||||||
|
title = 'Futurismic'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'Near-future science fiction and fact since 2001'
|
||||||
|
oldest_article = 15
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
language = 'en'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
publication_type = 'blog'
|
||||||
|
extra_css = ' body{font-family: Arial,Verdana,sans-serif} '
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : 'blog, sf'
|
||||||
|
, 'publisher': 'Futurismic'
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_attributes = ['width','height']
|
||||||
|
keep_only_tags = [dict(attrs={'class':['post','commentlist']})]
|
||||||
|
remove_tags = [dict(attrs={'class':['sociable','feedback','tagwords']})]
|
||||||
|
feeds = [(u'Posts', u'http://feeds2.feedburner.com/futurismic_feed')]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
return self.adeify_images(soup)
|
||||||
|
|
||||||
|
|
@ -657,9 +657,14 @@ class ActionEditCollections(InterfaceActionBase):
|
|||||||
name = 'Edit Collections'
|
name = 'Edit Collections'
|
||||||
actual_plugin = 'calibre.gui2.actions.edit_collections:EditCollectionsAction'
|
actual_plugin = 'calibre.gui2.actions.edit_collections:EditCollectionsAction'
|
||||||
|
|
||||||
|
class ActionCopyToLibrary(InterfaceActionBase):
|
||||||
|
name = 'Copy To Library'
|
||||||
|
actual_plugin = 'calibre.gui2.actions.copy_to_library:CopyToLibraryAction'
|
||||||
|
|
||||||
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
||||||
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
||||||
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
|
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
|
||||||
ActionRestart, ActionOpenFolder, ActionConnectShare,
|
ActionRestart, ActionOpenFolder, ActionConnectShare,
|
||||||
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
||||||
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary]
|
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
|
||||||
|
ActionCopyToLibrary]
|
||||||
|
@ -55,9 +55,9 @@ class ANDROID(USBMS):
|
|||||||
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
||||||
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX']
|
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX']
|
||||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
|
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
|
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
|
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
||||||
|
@ -80,6 +80,13 @@ class HANLINV3(USBMS):
|
|||||||
drives['carda'] = main
|
drives['carda'] = main
|
||||||
return drives
|
return drives
|
||||||
|
|
||||||
|
class SPECTRA(HANLINV3):
|
||||||
|
|
||||||
|
name = 'Spectra'
|
||||||
|
gui_name = 'Spectra'
|
||||||
|
PRODUCT_ID = [0xa4a5]
|
||||||
|
|
||||||
|
FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'djvu', 'pdf', 'rtf', 'txt']
|
||||||
|
|
||||||
class HANLINV5(HANLINV3):
|
class HANLINV5(HANLINV3):
|
||||||
name = 'Hanlin V5 driver'
|
name = 'Hanlin V5 driver'
|
||||||
|
@ -38,7 +38,7 @@ def author_to_author_sort(author):
|
|||||||
author = _bracket_pat.sub('', author).strip()
|
author = _bracket_pat.sub('', author).strip()
|
||||||
tokens = author.split()
|
tokens = author.split()
|
||||||
tokens = tokens[-1:] + tokens[:-1]
|
tokens = tokens[-1:] + tokens[:-1]
|
||||||
if len(tokens) > 1:
|
if len(tokens) > 1 and method != 'nocomma':
|
||||||
tokens[0] += ','
|
tokens[0] += ','
|
||||||
return ' '.join(tokens)
|
return ' '.join(tokens)
|
||||||
|
|
||||||
|
@ -26,8 +26,9 @@ class InterfaceAction(QObject):
|
|||||||
If two :class:`InterfaceAction` objects have the same name, the one with higher
|
If two :class:`InterfaceAction` objects have the same name, the one with higher
|
||||||
priority takes precedence.
|
priority takes precedence.
|
||||||
|
|
||||||
Sub-classes should implement the :meth:`genesis` and
|
Sub-classes should implement the :meth:`genesis`, :meth:`library_moved`,
|
||||||
:meth:`location_selected` methods.
|
:meth:`location_selected` :meth:`shutting_down`
|
||||||
|
and :meth:`initialization_complete` methods.
|
||||||
|
|
||||||
Once initialized, this plugin has access to the main calibre GUI via the
|
Once initialized, this plugin has access to the main calibre GUI via the
|
||||||
:attr:`gui` member. You can access other plugins by name, for example::
|
:attr:`gui` member. You can access other plugins by name, for example::
|
||||||
@ -108,3 +109,28 @@ class InterfaceAction(QObject):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def library_changed(self, db):
|
||||||
|
'''
|
||||||
|
Called whenever the current library is changed.
|
||||||
|
|
||||||
|
:param db: The LibraryDatabase corresponding to the current library.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialization_complete(self):
|
||||||
|
'''
|
||||||
|
Called once per action when the initialization of the main GUI is
|
||||||
|
completed.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def shutting_down(self):
|
||||||
|
'''
|
||||||
|
Called once per plugin when the main GUI is in the process of shutting
|
||||||
|
down. Release any used resources, but try not to block the shutdown for
|
||||||
|
long periods of time.
|
||||||
|
|
||||||
|
:return: False to halt the shutdown. You are responsible for telling
|
||||||
|
the user why the shutdown was halted.
|
||||||
|
'''
|
||||||
|
return True
|
||||||
|
@ -97,10 +97,21 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
ac.triggered.connect(partial(self.qs_requested, i))
|
ac.triggered.connect(partial(self.qs_requested, i))
|
||||||
self.choose_menu.addAction(ac)
|
self.choose_menu.addAction(ac)
|
||||||
|
|
||||||
def library_used(self, db):
|
def library_name(self):
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
|
path = db.library_path
|
||||||
|
if isbytestring(path):
|
||||||
|
path = path.decode(filesystem_encoding)
|
||||||
|
path = path.replace(os.sep, '/')
|
||||||
|
return self.stats.pretty(path)
|
||||||
|
|
||||||
|
def library_changed(self, db):
|
||||||
self.stats.library_used(db)
|
self.stats.library_used(db)
|
||||||
self.build_menus()
|
self.build_menus()
|
||||||
|
|
||||||
|
def initialization_complete(self):
|
||||||
|
self.library_changed(self.gui.library_view.model().db)
|
||||||
|
|
||||||
def build_menus(self):
|
def build_menus(self):
|
||||||
db = self.gui.library_view.model().db
|
db = self.gui.library_view.model().db
|
||||||
locations = list(self.stats.locations(db))
|
locations = list(self.stats.locations(db))
|
||||||
|
21
src/calibre/gui2/actions/copy_to_library.py
Normal file
21
src/calibre/gui2/actions/copy_to_library.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import QMenu
|
||||||
|
|
||||||
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
|
||||||
|
class CopyToLibraryAction(InterfaceAction):
|
||||||
|
|
||||||
|
name = 'Copy To Library'
|
||||||
|
action_spec = (_('Copy to library'), 'lt.png',
|
||||||
|
_('Copy selected books to the specified library'), None)
|
||||||
|
|
||||||
|
def genesis(self):
|
||||||
|
self.menu = QMenu(self.gui)
|
||||||
|
|
||||||
|
|
@ -174,8 +174,14 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
_('No books selected'))
|
_('No books selected'))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
return
|
return
|
||||||
if MetadataBulkDialog(self.gui, rows,
|
# Prevent the TagView from updating due to signals from the database
|
||||||
self.gui.library_view.model().db).changed:
|
self.gui.tags_view.blockSignals(True)
|
||||||
|
try:
|
||||||
|
changed = MetadataBulkDialog(self.gui, rows,
|
||||||
|
self.gui.library_view.model().db).changed
|
||||||
|
finally:
|
||||||
|
self.gui.tags_view.blockSignals(False)
|
||||||
|
if changed:
|
||||||
self.gui.library_view.model().resort(reset=False)
|
self.gui.library_view.model().resort(reset=False)
|
||||||
self.gui.library_view.model().research()
|
self.gui.library_view.model().research()
|
||||||
self.gui.tags_view.recount()
|
self.gui.tags_view.recount()
|
||||||
|
@ -31,7 +31,12 @@ class FetchNewsAction(InterfaceAction):
|
|||||||
self.qaction.setMenu(self.scheduler.news_menu)
|
self.qaction.setMenu(self.scheduler.news_menu)
|
||||||
self.qaction.triggered.connect(
|
self.qaction.triggered.connect(
|
||||||
self.scheduler.show_dialog)
|
self.scheduler.show_dialog)
|
||||||
self.database_changed = self.scheduler.database_changed
|
|
||||||
|
def library_changed(self, db):
|
||||||
|
self.scheduler.database_changed(db)
|
||||||
|
|
||||||
|
def initialization_complete(self):
|
||||||
|
self.connect_scheduler()
|
||||||
|
|
||||||
def connect_scheduler(self):
|
def connect_scheduler(self):
|
||||||
self.scheduler.delete_old_news.connect(
|
self.scheduler.delete_old_news.connect(
|
||||||
|
@ -196,6 +196,7 @@ class CoverFlowMixin(object):
|
|||||||
|
|
||||||
def show_cover_browser(self):
|
def show_cover_browser(self):
|
||||||
d = CBDialog(self, self.cover_flow)
|
d = CBDialog(self, self.cover_flow)
|
||||||
|
d.addAction(self.cb_splitter.action_toggle)
|
||||||
self.cover_flow.setVisible(True)
|
self.cover_flow.setVisible(True)
|
||||||
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
||||||
d.show()
|
d.show()
|
||||||
|
@ -11,7 +11,7 @@ from functools import partial
|
|||||||
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
|
||||||
QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
|
QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
|
||||||
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
||||||
QPushButton
|
QPushButton, QCoreApplication
|
||||||
|
|
||||||
from calibre.utils.date import qt_to_dt, now
|
from calibre.utils.date import qt_to_dt, now
|
||||||
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
|
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
|
||||||
@ -406,13 +406,17 @@ class BulkBase(Base):
|
|||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
if self.process_each_book():
|
if self.process_each_book():
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
|
QCoreApplication.processEvents()
|
||||||
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
||||||
self.db.set_custom(book_id, self.getter(val), num=self.col_id, notify=notify)
|
new_val = self.getter(val)
|
||||||
|
if set(val) != new_val:
|
||||||
|
self.db.set_custom(book_id, new_val, num=self.col_id, notify=notify)
|
||||||
else:
|
else:
|
||||||
val = self.getter()
|
val = self.getter()
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val:
|
if val != self.initial_val:
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
|
QCoreApplication.processEvents()
|
||||||
self.db.set_custom(book_id, val, num=self.col_id, notify=notify)
|
self.db.set_custom(book_id, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
class BulkBool(BulkBase, Bool):
|
class BulkBool(BulkBase, Bool):
|
||||||
@ -431,6 +435,7 @@ class BulkDateTime(BulkBase, DateTime):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class BulkSeries(BulkBase):
|
class BulkSeries(BulkBase):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower()))
|
values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower()))
|
||||||
@ -456,6 +461,7 @@ class BulkSeries(BulkBase):
|
|||||||
update_indices = self.idx_widget.checkState()
|
update_indices = self.idx_widget.checkState()
|
||||||
if val != '':
|
if val != '':
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
|
QCoreApplication.processEvents()
|
||||||
if update_indices:
|
if update_indices:
|
||||||
if tweaks['series_index_auto_increment'] == 'next':
|
if tweaks['series_index_auto_increment'] == 'next':
|
||||||
s_index = self.db.get_next_cc_series_num_for\
|
s_index = self.db.get_next_cc_series_num_for\
|
||||||
@ -544,8 +550,9 @@ class BulkText(BulkBase):
|
|||||||
ans = set(original_value)
|
ans = set(original_value)
|
||||||
ans -= set([v.strip() for v in
|
ans -= set([v.strip() for v in
|
||||||
unicode(self.removing_widget.tags_box.text()).split(',')])
|
unicode(self.removing_widget.tags_box.text()).split(',')])
|
||||||
ans |= set([v.strip() for v in
|
txt = unicode(self.adding_widget.text())
|
||||||
unicode(self.adding_widget.text()).split(',')])
|
if txt:
|
||||||
|
ans |= set([v.strip() for v in txt.split(',')])
|
||||||
return ans # returning a set instead of a list works, for now at least.
|
return ans # returning a set instead of a list works, for now at least.
|
||||||
val = unicode(self.widgets[1].currentText()).strip()
|
val = unicode(self.widgets[1].currentText()).strip()
|
||||||
if not val:
|
if not val:
|
||||||
|
@ -48,11 +48,11 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
self.refresh(row)
|
self.refresh(row)
|
||||||
|
|
||||||
def open_book_path(self, path):
|
def open_book_path(self, path):
|
||||||
if os.sep in unicode(path):
|
path = unicode(path)
|
||||||
|
if os.sep in path:
|
||||||
open_local_file(path)
|
open_local_file(path)
|
||||||
else:
|
else:
|
||||||
format = unicode(path)
|
path = self.view.model().db.format_abspath(self.current_row, path)
|
||||||
path = self.view.model().db.format_abspath(self.current_row, format)
|
|
||||||
if path is not None:
|
if path is not None:
|
||||||
open_local_file(path)
|
open_local_file(path)
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="fit_cover">
|
<widget class="QCheckBox" name="fit_cover">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Fit &cover to view</string>
|
<string>Fit &cover within view</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -3,14 +3,15 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
'''Dialog to edit metadata in bulk'''
|
'''Dialog to edit metadata in bulk'''
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, QObject
|
from PyQt4.Qt import SIGNAL, QObject, QDialog, QGridLayout, \
|
||||||
from PyQt4.QtGui import QDialog, QGridLayout
|
QCoreApplication
|
||||||
|
|
||||||
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
|
||||||
from calibre.ebooks.metadata import string_to_authors, \
|
from calibre.ebooks.metadata import string_to_authors, \
|
||||||
authors_to_string
|
authors_to_string
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
|
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||||
|
|
||||||
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||||
|
|
||||||
@ -25,10 +26,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
len(rows))
|
len(rows))
|
||||||
self.write_series = False
|
self.write_series = False
|
||||||
self.changed = False
|
self.changed = False
|
||||||
QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync)
|
|
||||||
|
|
||||||
self.tags.update_tags_cache(self.db.all_tags())
|
all_tags = self.db.all_tags()
|
||||||
self.remove_tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_tags_cache(all_tags)
|
||||||
|
self.remove_tags.update_tags_cache(all_tags)
|
||||||
|
|
||||||
self.initialize_combos()
|
self.initialize_combos()
|
||||||
|
|
||||||
@ -102,43 +103,40 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.tags.update_tags_cache(self.db.all_tags())
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
self.remove_tags.update_tags_cache(self.db.all_tags())
|
self.remove_tags.update_tags_cache(self.db.all_tags())
|
||||||
|
|
||||||
def sync(self):
|
def accept(self):
|
||||||
for id in self.ids:
|
if len(self.ids) < 1:
|
||||||
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
pd = ProgressDialog(_('Working'),
|
||||||
|
_('Applying changes to %d books. This may take a while.')%len(self.ids),
|
||||||
|
0, 0, self, cancelable=False)
|
||||||
|
pd.setModal(True)
|
||||||
|
pd.show()
|
||||||
|
def upd():
|
||||||
|
QCoreApplication.processEvents()
|
||||||
|
|
||||||
|
try:
|
||||||
|
remove = unicode(self.remove_tags.text()).strip().split(',')
|
||||||
|
add = unicode(self.tags.text()).strip().split(',')
|
||||||
au = unicode(self.authors.text())
|
au = unicode(self.authors.text())
|
||||||
if au:
|
|
||||||
au = string_to_authors(au)
|
|
||||||
self.db.set_authors(id, au, notify=False)
|
|
||||||
if self.auto_author_sort.isChecked():
|
|
||||||
x = self.db.author_sort_from_book(id, index_is_id=True)
|
|
||||||
if x:
|
|
||||||
self.db.set_author_sort(id, x, notify=False)
|
|
||||||
aus = unicode(self.author_sort.text())
|
aus = unicode(self.author_sort.text())
|
||||||
if aus and self.author_sort.isEnabled():
|
do_aus = self.author_sort.isEnabled()
|
||||||
self.db.set_author_sort(id, aus, notify=False)
|
rating = self.rating.value()
|
||||||
if self.rating.value() != -1:
|
|
||||||
self.db.set_rating(id, 2*self.rating.value(), notify=False)
|
|
||||||
pub = unicode(self.publisher.text())
|
pub = unicode(self.publisher.text())
|
||||||
if pub:
|
do_series = self.write_series
|
||||||
self.db.set_publisher(id, pub, notify=False)
|
|
||||||
remove_tags = unicode(self.remove_tags.text()).strip()
|
|
||||||
if remove_tags:
|
|
||||||
remove_tags = [i.strip() for i in remove_tags.split(',')]
|
|
||||||
self.db.unapply_tags(id, remove_tags, notify=False)
|
|
||||||
tags = unicode(self.tags.text()).strip()
|
|
||||||
if tags:
|
|
||||||
tags = map(lambda x: x.strip(), tags.split(','))
|
|
||||||
self.db.set_tags(id, tags, append=True, notify=False)
|
|
||||||
if self.write_series:
|
|
||||||
series = unicode(self.series.currentText()).strip()
|
series = unicode(self.series.currentText()).strip()
|
||||||
next = self.db.get_next_series_num_for(series)
|
do_autonumber = self.autonumber_series.isChecked()
|
||||||
self.db.set_series(id, series, notify=False)
|
do_remove_format = self.remove_format.currentIndex() > -1
|
||||||
num = next if self.autonumber_series.isChecked() and series else 1.0
|
remove_format = unicode(self.remove_format.currentText())
|
||||||
self.db.set_series_index(id, num, notify=False)
|
do_swap_ta = self.swap_title_and_author.isChecked()
|
||||||
|
do_remove_conv = self.remove_conversion_settings.isChecked()
|
||||||
|
do_auto_author = self.auto_author_sort.isChecked()
|
||||||
|
|
||||||
if self.remove_format.currentIndex() > -1:
|
upd()
|
||||||
self.db.remove_format(id, unicode(self.remove_format.currentText()), index_is_id=True, notify=False)
|
self.changed = bool(self.ids)
|
||||||
|
for id in self.ids:
|
||||||
if self.swap_title_and_author.isChecked():
|
upd()
|
||||||
|
if do_swap_ta:
|
||||||
title = self.db.title(id, index_is_id=True)
|
title = self.db.title(id, index_is_id=True)
|
||||||
aum = self.db.authors(id, index_is_id=True)
|
aum = self.db.authors(id, index_is_id=True)
|
||||||
if aum:
|
if aum:
|
||||||
@ -148,13 +146,55 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
if title:
|
if title:
|
||||||
new_authors = string_to_authors(title)
|
new_authors = string_to_authors(title)
|
||||||
self.db.set_authors(id, new_authors, notify=False)
|
self.db.set_authors(id, new_authors, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
if self.remove_conversion_settings.isChecked():
|
if au:
|
||||||
|
self.db.set_authors(id, string_to_authors(au), notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if do_auto_author:
|
||||||
|
x = self.db.author_sort_from_book(id, index_is_id=True)
|
||||||
|
if x:
|
||||||
|
self.db.set_author_sort(id, x, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if aus and do_aus:
|
||||||
|
self.db.set_author_sort(id, aus, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if rating != -1:
|
||||||
|
self.db.set_rating(id, 2*rating, notify=False)
|
||||||
|
|
||||||
|
if pub:
|
||||||
|
self.db.set_publisher(id, pub, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if do_series:
|
||||||
|
next = self.db.get_next_series_num_for(series)
|
||||||
|
self.db.set_series(id, series, notify=False)
|
||||||
|
num = next if do_autonumber and series else 1.0
|
||||||
|
self.db.set_series_index(id, num, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if do_remove_format:
|
||||||
|
self.db.remove_format(id, remove_format, index_is_id=True, notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
if do_remove_conv:
|
||||||
self.db.delete_conversion_options(id, 'PIPE')
|
self.db.delete_conversion_options(id, 'PIPE')
|
||||||
|
|
||||||
self.changed = True
|
upd()
|
||||||
for w in getattr(self, 'custom_column_widgets', []):
|
for w in getattr(self, 'custom_column_widgets', []):
|
||||||
w.commit(self.ids)
|
w.commit(self.ids)
|
||||||
|
self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
|
||||||
|
notify=False)
|
||||||
|
upd()
|
||||||
|
|
||||||
|
|
||||||
|
finally:
|
||||||
|
pd.hide()
|
||||||
|
|
||||||
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
def series_changed(self):
|
def series_changed(self):
|
||||||
|
@ -13,7 +13,8 @@ class ProgressDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
canceled_signal = pyqtSignal()
|
canceled_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, title, msg='', min=0, max=99, parent=None):
|
def __init__(self, title, msg='', min=0, max=99, parent=None,
|
||||||
|
cancelable=True):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
@ -26,6 +27,9 @@ class ProgressDialog(QDialog, Ui_Dialog):
|
|||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
|
||||||
self.button_box.rejected.connect(self._canceled)
|
self.button_box.rejected.connect(self._canceled)
|
||||||
|
if not cancelable:
|
||||||
|
self.button_box.setVisible(False)
|
||||||
|
self.cancelable = cancelable
|
||||||
|
|
||||||
def set_msg(self, msg=''):
|
def set_msg(self, msg=''):
|
||||||
self.message.setText(msg)
|
self.message.setText(msg)
|
||||||
@ -54,8 +58,14 @@ class ProgressDialog(QDialog, Ui_Dialog):
|
|||||||
self.title.setText(_('Aborting...'))
|
self.title.setText(_('Aborting...'))
|
||||||
self.canceled_signal.emit()
|
self.canceled_signal.emit()
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
if not self.cancelable:
|
||||||
|
return
|
||||||
|
QDialog.reject(self)
|
||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
if ev.key() == Qt.Key_Escape:
|
if ev.key() == Qt.Key_Escape:
|
||||||
|
if self.cancelable:
|
||||||
self._canceled()
|
self._canceled()
|
||||||
else:
|
else:
|
||||||
QDialog.keyPressEvent(self, ev)
|
QDialog.keyPressEvent(self, ev)
|
||||||
|
@ -14,7 +14,8 @@ from Queue import Empty, Queue
|
|||||||
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
||||||
QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
||||||
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \
|
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \
|
||||||
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication
|
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, \
|
||||||
|
QByteArray
|
||||||
|
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.utils.ipc.job import ParallelJob
|
from calibre.utils.ipc.job import ParallelJob
|
||||||
@ -281,6 +282,7 @@ class JobsButton(QFrame):
|
|||||||
self.pi = ProgressIndicator(self, size)
|
self.pi = ProgressIndicator(self, size)
|
||||||
self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
|
self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
|
||||||
self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
|
self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
|
||||||
|
self.shortcut = _('Shift+Alt+J')
|
||||||
|
|
||||||
if horizontal:
|
if horizontal:
|
||||||
self.setLayout(QHBoxLayout())
|
self.setLayout(QHBoxLayout())
|
||||||
@ -297,15 +299,24 @@ class JobsButton(QFrame):
|
|||||||
self.layout().setMargin(0)
|
self.layout().setMargin(0)
|
||||||
self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
self.setToolTip(_('Click to see list of active jobs.'))
|
b = _('Click to see list of jobs')
|
||||||
|
self.setToolTip(b + u' (%s)'%self.shortcut)
|
||||||
|
self.action_toggle = QAction(b, parent)
|
||||||
|
parent.addAction(self.action_toggle)
|
||||||
|
self.action_toggle.setShortcut(self.shortcut)
|
||||||
|
self.action_toggle.triggered.connect(self.toggle)
|
||||||
|
|
||||||
def initialize(self, jobs_dialog, job_manager):
|
def initialize(self, jobs_dialog, job_manager):
|
||||||
self.jobs_dialog = jobs_dialog
|
self.jobs_dialog = jobs_dialog
|
||||||
job_manager.job_added.connect(self.job_added)
|
job_manager.job_added.connect(self.job_added)
|
||||||
job_manager.job_done.connect(self.job_done)
|
job_manager.job_done.connect(self.job_done)
|
||||||
|
self.jobs_dialog.addAction(self.action_toggle)
|
||||||
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
|
self.toggle()
|
||||||
|
|
||||||
|
def toggle(self, *args):
|
||||||
if self.jobs_dialog.isVisible():
|
if self.jobs_dialog.isVisible():
|
||||||
self.jobs_dialog.hide()
|
self.jobs_dialog.hide()
|
||||||
else:
|
else:
|
||||||
@ -365,13 +376,27 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
||||||
self.jobs_view.doubleClicked.connect(self.show_job_details)
|
self.jobs_view.doubleClicked.connect(self.show_job_details)
|
||||||
self.jobs_view.horizontalHeader().setMovable(True)
|
self.jobs_view.horizontalHeader().setMovable(True)
|
||||||
state = gprefs.get('jobs view column layout', None)
|
self.restore_state()
|
||||||
if state is not None:
|
|
||||||
|
def restore_state(self):
|
||||||
try:
|
try:
|
||||||
self.jobs_view.horizontalHeader().restoreState(bytes(state))
|
geom = gprefs.get('jobs_dialog_geometry', bytearray(''))
|
||||||
|
self.restoreGeometry(QByteArray(geom))
|
||||||
|
state = gprefs.get('jobs view column layout', bytearray(''))
|
||||||
|
self.jobs_view.horizontalHeader().restoreState(QByteArray(state))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
try:
|
||||||
|
state = bytearray(self.jobs_view.horizontalHeader().saveState())
|
||||||
|
gprefs['jobs view column layout'] = state
|
||||||
|
geom = bytearray(self.saveGeometry())
|
||||||
|
gprefs['jobs_dialog_geometry'] = geom
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def show_job_details(self, index):
|
def show_job_details(self, index):
|
||||||
row = index.row()
|
row = index.row()
|
||||||
job = self.jobs_view.model().row_to_job(row)
|
job = self.jobs_view.model().row_to_job(row)
|
||||||
@ -394,9 +419,13 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.model.kill_all_jobs()
|
self.model.kill_all_jobs()
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
try:
|
self.save_state()
|
||||||
state = bytearray(self.jobs_view.horizontalHeader().saveState())
|
return QDialog.closeEvent(self, e)
|
||||||
gprefs['jobs view column layout'] = state
|
|
||||||
except:
|
def show(self, *args):
|
||||||
pass
|
self.restore_state()
|
||||||
e.accept()
|
return QDialog.show(self, *args)
|
||||||
|
|
||||||
|
def hide(self, *args):
|
||||||
|
self.save_state()
|
||||||
|
return QDialog.hide(self, *args)
|
||||||
|
@ -78,6 +78,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.setAnimated(True)
|
self.setAnimated(True)
|
||||||
self.setHeaderHidden(True)
|
self.setHeaderHidden(True)
|
||||||
self.setItemDelegate(TagDelegate(self))
|
self.setItemDelegate(TagDelegate(self))
|
||||||
|
self.made_connections = False
|
||||||
|
|
||||||
def set_database(self, db, tag_match, sort_by):
|
def set_database(self, db, tag_match, sort_by):
|
||||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||||
@ -90,12 +91,14 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.search_restriction = None
|
self.search_restriction = None
|
||||||
self.setModel(self._model)
|
self.setModel(self._model)
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
self.clicked.connect(self.toggle)
|
|
||||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
|
||||||
pop = config['sort_tags_by']
|
pop = config['sort_tags_by']
|
||||||
self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop))
|
self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop))
|
||||||
self.sort_by.currentIndexChanged.connect(self.sort_changed)
|
if not self.made_connections:
|
||||||
|
self.clicked.connect(self.toggle)
|
||||||
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
|
self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
|
||||||
|
self.sort_by.currentIndexChanged.connect(self.sort_changed)
|
||||||
|
self.made_connections = True
|
||||||
db.add_listener(self.database_changed)
|
db.add_listener(self.database_changed)
|
||||||
|
|
||||||
def database_changed(self, event, ids):
|
def database_changed(self, event, ids):
|
||||||
|
@ -132,7 +132,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
# Jobs Button {{{
|
# Jobs Button {{{
|
||||||
self.job_manager = JobManager()
|
self.job_manager = JobManager()
|
||||||
self.jobs_dialog = JobsDialog(self, self.job_manager)
|
self.jobs_dialog = JobsDialog(self, self.job_manager)
|
||||||
self.jobs_button = JobsButton(horizontal=True)
|
self.jobs_button = JobsButton(horizontal=True, parent=self)
|
||||||
self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
|
self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -249,9 +249,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
self.read_settings()
|
self.read_settings()
|
||||||
self.finalize_layout()
|
self.finalize_layout()
|
||||||
self.donate_button.start_animation()
|
self.donate_button.start_animation()
|
||||||
|
self.set_window_title()
|
||||||
|
|
||||||
self.iactions['Fetch News'].connect_scheduler()
|
for ac in self.iactions.values():
|
||||||
self.iactions['Choose Library'].library_used(self.library_view.model().db)
|
ac.initialization_complete()
|
||||||
|
|
||||||
def start_content_server(self):
|
def start_content_server(self):
|
||||||
from calibre.library.server.main import start_threaded_server
|
from calibre.library.server.main import start_threaded_server
|
||||||
@ -351,8 +352,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
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):
|
def library_moved(self, newloc):
|
||||||
if newloc is None: return
|
if newloc is None: return
|
||||||
db = LibraryDatabase2(newloc)
|
db = LibraryDatabase2(newloc)
|
||||||
@ -367,10 +366,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear_to_help()
|
||||||
self.book_details.reset_info()
|
self.book_details.reset_info()
|
||||||
self.library_view.model().count_changed()
|
self.library_view.model().count_changed()
|
||||||
self.iactions['Fetch News'].database_changed(db)
|
|
||||||
prefs['library_path'] = self.library_path
|
prefs['library_path'] = self.library_path
|
||||||
self.iactions['Choose Library'].library_used(self.library_view.model().db)
|
db = self.library_view.model().db
|
||||||
|
for action in self.iactions.values():
|
||||||
|
action.library_changed(db)
|
||||||
|
self.set_window_title()
|
||||||
|
|
||||||
|
def set_window_title(self):
|
||||||
|
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())
|
||||||
|
|
||||||
def location_selected(self, location):
|
def location_selected(self, location):
|
||||||
'''
|
'''
|
||||||
@ -511,6 +514,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
|
|
||||||
|
|
||||||
def shutdown(self, write_settings=True):
|
def shutdown(self, write_settings=True):
|
||||||
|
for action in self.iactions.values():
|
||||||
|
if not action.shutting_down():
|
||||||
|
return
|
||||||
if write_settings:
|
if write_settings:
|
||||||
self.write_settings()
|
self.write_settings()
|
||||||
self.check_messages_timer.stop()
|
self.check_messages_timer.stop()
|
||||||
|
@ -871,14 +871,14 @@ class LayoutButton(QToolButton):
|
|||||||
def set_state_to_show(self, *args):
|
def set_state_to_show(self, *args):
|
||||||
self.setChecked(False)
|
self.setChecked(False)
|
||||||
label =_('Show')
|
label =_('Show')
|
||||||
self.setText(label + ' ' + self.label + ' ' + self.shortcut)
|
self.setText(label + ' ' + self.label + u' (%s)'%self.shortcut)
|
||||||
self.setToolTip(self.text())
|
self.setToolTip(self.text())
|
||||||
self.setStatusTip(self.text())
|
self.setStatusTip(self.text())
|
||||||
|
|
||||||
def set_state_to_hide(self, *args):
|
def set_state_to_hide(self, *args):
|
||||||
self.setChecked(True)
|
self.setChecked(True)
|
||||||
label = _('Hide')
|
label = _('Hide')
|
||||||
self.setText(label + ' ' + self.label+ ' ' + self.shortcut)
|
self.setText(label + ' ' + self.label+ u' (%s)'%self.shortcut)
|
||||||
self.setToolTip(self.text())
|
self.setToolTip(self.text())
|
||||||
self.setStatusTip(self.text())
|
self.setStatusTip(self.text())
|
||||||
|
|
||||||
@ -941,7 +941,10 @@ class Splitter(QSplitter):
|
|||||||
@property
|
@property
|
||||||
def is_side_index_hidden(self):
|
def is_side_index_hidden(self):
|
||||||
sizes = list(self.sizes())
|
sizes = list(self.sizes())
|
||||||
|
try:
|
||||||
return sizes[self.side_index] == 0
|
return sizes[self.side_index] == 0
|
||||||
|
except IndexError:
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def save_name(self):
|
def save_name(self):
|
||||||
|
@ -26,7 +26,7 @@ from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
|||||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
|
from calibre import isbytestring
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
|
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
@ -116,6 +116,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# so that various code taht connects directly will not complain about
|
# so that various code taht connects directly will not complain about
|
||||||
# missing functions
|
# missing functions
|
||||||
self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter')
|
self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter')
|
||||||
|
# Store temporary tables in memory
|
||||||
|
self.conn.execute('pragma temp_store=2')
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def exists_at(cls, path):
|
def exists_at(cls, path):
|
||||||
@ -1369,6 +1372,80 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return set([])
|
return set([])
|
||||||
return set([r[0] for r in result])
|
return set([r[0] for r in result])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cleanup_tags(cls, tags):
|
||||||
|
tags = [x.strip() for x in tags if x.strip()]
|
||||||
|
tags = [x.decode(preferred_encoding, 'replace') \
|
||||||
|
if isbytestring(x) else x for x in tags]
|
||||||
|
tags = [u' '.join(x.split()) for x in tags]
|
||||||
|
ans, seen = [], set([])
|
||||||
|
for tag in tags:
|
||||||
|
if tag.lower() not in seen:
|
||||||
|
seen.add(tag.lower())
|
||||||
|
ans.append(tag)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def bulk_modify_tags(self, ids, add=[], remove=[], notify=False):
|
||||||
|
add = self.cleanup_tags(add)
|
||||||
|
remove = self.cleanup_tags(remove)
|
||||||
|
remove = set(remove) - set(add)
|
||||||
|
if not ids or (not add and not remove):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add tags that do not already exist into the tag table
|
||||||
|
all_tags = self.all_tags()
|
||||||
|
lt = [t.lower() for t in all_tags]
|
||||||
|
new_tags = [t for t in add if t.lower() not in lt]
|
||||||
|
if new_tags:
|
||||||
|
self.conn.executemany('INSERT INTO tags(name) VALUES (?)', [(x,) for x in
|
||||||
|
new_tags])
|
||||||
|
|
||||||
|
# Create the temporary tables to store the ids for books and tags
|
||||||
|
# to be operated on
|
||||||
|
tables = ('temp_bulk_tag_edit_books', 'temp_bulk_tag_edit_add',
|
||||||
|
'temp_bulk_tag_edit_remove')
|
||||||
|
drops = '\n'.join(['DROP TABLE IF EXISTS %s;'%t for t in tables])
|
||||||
|
creates = '\n'.join(['CREATE TEMP TABLE %s(id INTEGER PRIMARY KEY);'%t
|
||||||
|
for t in tables])
|
||||||
|
self.conn.executescript(drops + creates)
|
||||||
|
|
||||||
|
# Populate the books temp table
|
||||||
|
self.conn.executemany(
|
||||||
|
'INSERT INTO temp_bulk_tag_edit_books VALUES (?)',
|
||||||
|
[(x,) for x in ids])
|
||||||
|
|
||||||
|
# Populate the add/remove tags temp tables
|
||||||
|
for table, tags in enumerate([add, remove]):
|
||||||
|
if not tags:
|
||||||
|
continue
|
||||||
|
table = tables[table+1]
|
||||||
|
insert = ('INSERT INTO %s(id) SELECT tags.id FROM tags WHERE name=?'
|
||||||
|
' COLLATE PYNOCASE LIMIT 1')
|
||||||
|
self.conn.executemany(insert%table, [(x,) for x in tags])
|
||||||
|
|
||||||
|
if remove:
|
||||||
|
self.conn.execute(
|
||||||
|
'''DELETE FROM books_tags_link WHERE
|
||||||
|
book IN (SELECT id FROM %s) AND
|
||||||
|
tag IN (SELECT id FROM %s)'''
|
||||||
|
% (tables[0], tables[2]))
|
||||||
|
|
||||||
|
if add:
|
||||||
|
self.conn.execute(
|
||||||
|
'''
|
||||||
|
INSERT INTO books_tags_link(book, tag) SELECT {0}.id, {1}.id FROM
|
||||||
|
{0}, {1}
|
||||||
|
'''.format(tables[0], tables[1])
|
||||||
|
)
|
||||||
|
self.conn.executescript(drops)
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
for x in ids:
|
||||||
|
tags = u','.join(self.get_tags(x))
|
||||||
|
self.data.set(x, self.FIELD_MAP['tags'], tags, row_is_id=True)
|
||||||
|
if notify:
|
||||||
|
self.notify('metadata', ids)
|
||||||
|
|
||||||
def set_tags(self, id, tags, append=False, notify=True):
|
def set_tags(self, id, tags, append=False, notify=True):
|
||||||
'''
|
'''
|
||||||
@param tags: list of strings
|
@param tags: list of strings
|
||||||
@ -1378,10 +1455,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,))
|
||||||
self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1')
|
self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1')
|
||||||
otags = self.get_tags(id)
|
otags = self.get_tags(id)
|
||||||
tags = [x.strip() for x in tags if x.strip()]
|
tags = self.cleanup_tags(tags)
|
||||||
tags = [x.decode(preferred_encoding, 'replace') if not isinstance(x,
|
|
||||||
unicode) else x for x in tags]
|
|
||||||
tags = [u' '.join(x.split()) for x in tags]
|
|
||||||
for tag in (set(tags)-otags):
|
for tag in (set(tags)-otags):
|
||||||
tag = tag.strip()
|
tag = tag.strip()
|
||||||
if not tag:
|
if not tag:
|
||||||
@ -1407,7 +1481,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
|
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
|
||||||
(id, tid))
|
(id, tid))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
tags = ','.join(self.get_tags(id))
|
tags = u','.join(self.get_tags(id))
|
||||||
self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
@ -13,10 +13,12 @@ from threading import Thread
|
|||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.date import parse_date, isoformat
|
from calibre.utils.date import parse_date, isoformat
|
||||||
|
from calibre import isbytestring
|
||||||
|
|
||||||
global_lock = RLock()
|
global_lock = RLock()
|
||||||
|
|
||||||
@ -98,6 +100,19 @@ def _author_to_author_sort(x):
|
|||||||
if not x: return ''
|
if not x: return ''
|
||||||
return author_to_author_sort(x.replace('|', ','))
|
return author_to_author_sort(x.replace('|', ','))
|
||||||
|
|
||||||
|
def pynocase(one, two, encoding='utf-8'):
|
||||||
|
if isbytestring(one):
|
||||||
|
try:
|
||||||
|
one = one.decode(encoding, 'replace')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if isbytestring(two):
|
||||||
|
try:
|
||||||
|
two = two.decode(encoding, 'replace')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return cmp(one.lower(), two.lower())
|
||||||
|
|
||||||
class DBThread(Thread):
|
class DBThread(Thread):
|
||||||
|
|
||||||
CLOSE = '-------close---------'
|
CLOSE = '-------close---------'
|
||||||
@ -115,10 +130,13 @@ class DBThread(Thread):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
self.conn = sqlite.connect(self.path, factory=Connection,
|
self.conn = sqlite.connect(self.path, factory=Connection,
|
||||||
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
|
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
|
||||||
|
encoding = self.conn.execute('pragma encoding').fetchone()[0]
|
||||||
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
|
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
|
||||||
self.conn.create_aggregate('concat', 1, Concatenate)
|
self.conn.create_aggregate('concat', 1, Concatenate)
|
||||||
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate)
|
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate)
|
||||||
self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate)
|
self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate)
|
||||||
|
self.conn.create_collation('PYNOCASE', partial(pynocase,
|
||||||
|
encoding=encoding))
|
||||||
if tweaks['title_series_sorting'] == 'strictly_alphabetic':
|
if tweaks['title_series_sorting'] == 'strictly_alphabetic':
|
||||||
self.conn.create_function('title_sort', 1, lambda x:x)
|
self.conn.create_function('title_sort', 1, lambda x:x)
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user