mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
c056b33814
@ -71,3 +71,10 @@ gui_pubdate_display_format = 'MMM yyyy'
|
|||||||
# order until the title is edited. Double-clicking on a title and hitting return
|
# order until the title is edited. Double-clicking on a title and hitting return
|
||||||
# without changing anything is sufficient to change the sort.
|
# without changing anything is sufficient to change the sort.
|
||||||
title_series_sorting = 'library_order'
|
title_series_sorting = 'library_order'
|
||||||
|
|
||||||
|
# How to render average rating in the tag browser.
|
||||||
|
# There are two rendering methods available. The first is to show a partial
|
||||||
|
# star, and the second is to show a partially filled rectangle. The first is
|
||||||
|
# better looking, but uses more screen space than the second.
|
||||||
|
# Values are 'star' or 'rectangle'
|
||||||
|
render_avg_rating_using='star'
|
||||||
|
@ -30,7 +30,7 @@ def authors_to_string(authors):
|
|||||||
|
|
||||||
def author_to_author_sort(author):
|
def author_to_author_sort(author):
|
||||||
method = tweaks['author_sort_copy_method']
|
method = tweaks['author_sort_copy_method']
|
||||||
if method == 'copy' or (method == 'comma' and author.count(',') > 0):
|
if method == 'copy' or (method == 'comma' and ',' in author):
|
||||||
return author
|
return author
|
||||||
tokens = author.split()
|
tokens = author.split()
|
||||||
tokens = tokens[-1:] + tokens[:-1]
|
tokens = tokens[-1:] + tokens[:-1]
|
||||||
|
@ -741,7 +741,7 @@ class OPF(object):
|
|||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
for tag in list(self.tags_path(self.metadata)):
|
for tag in list(self.tags_path(self.metadata)):
|
||||||
self.metadata.remove(tag)
|
tag.getparent().remove(tag)
|
||||||
for tag in val:
|
for tag in val:
|
||||||
elem = self.create_metadata_element('subject')
|
elem = self.create_metadata_element('subject')
|
||||||
self.set_text(elem, unicode(tag))
|
self.set_text(elem, unicode(tag))
|
||||||
|
@ -101,6 +101,8 @@ def _config():
|
|||||||
help=_('tag browser categories not to display'))
|
help=_('tag browser categories not to display'))
|
||||||
c.add_opt('gui_layout', choices=['wide', 'narrow'],
|
c.add_opt('gui_layout', choices=['wide', 'narrow'],
|
||||||
help=_('The layout of the user interface'), default='wide')
|
help=_('The layout of the user interface'), default='wide')
|
||||||
|
c.add_opt('show_avg_rating', default=True,
|
||||||
|
help=_('Show the average rating per item indication in the tag browser'))
|
||||||
return ConfigProxy(c)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
config = _config()
|
config = _config()
|
||||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import QPixmap, SIGNAL
|
|||||||
from calibre.gui2 import choose_images, error_dialog
|
from calibre.gui2 import choose_images, error_dialog
|
||||||
from calibre.gui2.convert.metadata_ui import Ui_Form
|
from calibre.gui2.convert.metadata_ui import Ui_Form
|
||||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
|
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
|
||||||
MetaInformation, authors_to_sort_string
|
MetaInformation
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
@ -57,7 +57,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
au = unicode(self.author.currentText())
|
au = unicode(self.author.currentText())
|
||||||
au = re.sub(r'\s+et al\.$', '', au)
|
au = re.sub(r'\s+et al\.$', '', au)
|
||||||
authors = string_to_authors(au)
|
authors = string_to_authors(au)
|
||||||
self.author_sort.setText(authors_to_sort_string(authors))
|
self.author_sort.setText(self.db.author_sort_from_authors(authors))
|
||||||
|
|
||||||
|
|
||||||
def initialize_metadata_options(self):
|
def initialize_metadata_options(self):
|
||||||
|
@ -23,7 +23,7 @@ from calibre.devices.scanner import DeviceScanner
|
|||||||
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
||||||
pixmap_to_data, warning_dialog, \
|
pixmap_to_data, warning_dialog, \
|
||||||
question_dialog, info_dialog, choose_dir
|
question_dialog, info_dialog, choose_dir
|
||||||
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre import preferred_encoding, prints
|
from calibre import preferred_encoding, prints
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.devices.errors import FreeSpaceError
|
from calibre.devices.errors import FreeSpaceError
|
||||||
@ -1409,7 +1409,7 @@ class DeviceMixin(object): # {{{
|
|||||||
# Set author_sort if it isn't already
|
# Set author_sort if it isn't already
|
||||||
asort = getattr(book, 'author_sort', None)
|
asort = getattr(book, 'author_sort', None)
|
||||||
if not asort and book.authors:
|
if not asort and book.authors:
|
||||||
book.author_sort = authors_to_sort_string(book.authors)
|
book.author_sort = self.db.author_sort_from_authors(book.authors)
|
||||||
resend_metadata = True
|
resend_metadata = True
|
||||||
|
|
||||||
if resend_metadata:
|
if resend_metadata:
|
||||||
|
@ -481,6 +481,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit'])
|
self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit'])
|
||||||
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
||||||
self.port.editingFinished.connect(self.check_port_value)
|
self.port.editingFinished.connect(self.check_port_value)
|
||||||
|
self.search_as_you_type.setChecked(config['search_as_you_type'])
|
||||||
|
self.show_avg_rating.setChecked(config['show_avg_rating'])
|
||||||
self.show_splash_screen.setChecked(gprefs.get('show_splash_screen',
|
self.show_splash_screen.setChecked(gprefs.get('show_splash_screen',
|
||||||
True))
|
True))
|
||||||
li = None
|
li = None
|
||||||
@ -862,6 +864,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
||||||
config['upload_news_to_device'] = self.sync_news.isChecked()
|
config['upload_news_to_device'] = self.sync_news.isChecked()
|
||||||
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||||
|
config['show_avg_rating'] = self.show_avg_rating.isChecked()
|
||||||
config['get_social_metadata'] = self.opt_get_social_metadata.isChecked()
|
config['get_social_metadata'] = self.opt_get_social_metadata.isChecked()
|
||||||
config['overwrite_author_title_metadata'] = self.opt_overwrite_author_title_metadata.isChecked()
|
config['overwrite_author_title_metadata'] = self.opt_overwrite_author_title_metadata.isChecked()
|
||||||
config['enforce_cpu_limit'] = bool(self.opt_enforce_cpu_limit.isChecked())
|
config['enforce_cpu_limit'] = bool(self.opt_enforce_cpu_limit.isChecked())
|
||||||
|
@ -371,6 +371,16 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="show_avg_rating">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show &average ratings in the tags browser</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
<widget class="QCheckBox" name="search_as_you_type">
|
<widget class="QCheckBox" name="search_as_you_type">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Search as you type</string>
|
<string>Search as you type</string>
|
||||||
@ -380,21 +390,21 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0" colspan="2">
|
<item row="7" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="sync_news">
|
<widget class="QCheckBox" name="sync_news">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically send downloaded &news to ebook reader</string>
|
<string>Automatically send downloaded &news to ebook reader</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="0" colspan="2">
|
<item row="8" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="delete_news">
|
<widget class="QCheckBox" name="delete_news">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Delete news from library when it is automatically sent to reader</string>
|
<string>&Delete news from library when it is automatically sent to reader</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0" colspan="2">
|
<item row="9" column="0" colspan="2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
@ -411,7 +421,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="0" colspan="2">
|
<item row="10" column="0" colspan="2">
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Toolbar</string>
|
<string>Toolbar</string>
|
||||||
@ -459,7 +469,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="10" column="0" colspan="2">
|
<item row="11" column="0" colspan="2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
82
src/calibre/gui2/dialogs/edit_authors_dialog.py
Normal file
82
src/calibre/gui2/dialogs/edit_authors_dialog.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import author_to_author_sort
|
||||||
|
from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog
|
||||||
|
|
||||||
|
class tableItem(QTableWidgetItem):
|
||||||
|
def __ge__(self, other):
|
||||||
|
return unicode(self.text()).lower() >= unicode(other.text()).lower()
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return unicode(self.text()).lower() < unicode(other.text()).lower()
|
||||||
|
|
||||||
|
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, db, id_to_select):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
Ui_EditAuthorsDialog.__init__(self)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.buttonBox.accepted.connect(self.accepted)
|
||||||
|
|
||||||
|
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
|
self.table.setColumnCount(2)
|
||||||
|
self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort')])
|
||||||
|
|
||||||
|
self.authors = {}
|
||||||
|
auts = db.get_authors_with_ids()
|
||||||
|
self.table.setRowCount(len(auts))
|
||||||
|
select_item = None
|
||||||
|
for row, (id, author, sort) in enumerate(auts):
|
||||||
|
author = author.replace('|', ',')
|
||||||
|
self.authors[id] = (author, sort)
|
||||||
|
aut = tableItem(author)
|
||||||
|
aut.setData(Qt.UserRole, id)
|
||||||
|
sort = tableItem(sort)
|
||||||
|
self.table.setItem(row, 0, aut)
|
||||||
|
self.table.setItem(row, 1, sort)
|
||||||
|
if id == id_to_select:
|
||||||
|
select_item = sort
|
||||||
|
self.table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
# set up the signal after the table is filled
|
||||||
|
self.table.cellChanged.connect(self.cell_changed)
|
||||||
|
|
||||||
|
self.table.setSortingEnabled(True)
|
||||||
|
self.table.sortByColumn(1, Qt.AscendingOrder)
|
||||||
|
if select_item is not None:
|
||||||
|
self.table.setCurrentItem(select_item)
|
||||||
|
self.table.editItem(select_item)
|
||||||
|
else:
|
||||||
|
self.table.setCurrentCell(0, 0)
|
||||||
|
|
||||||
|
def accepted(self):
|
||||||
|
self.result = []
|
||||||
|
for row in range(0,self.table.rowCount()):
|
||||||
|
id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
|
||||||
|
aut = unicode(self.table.item(row, 0).text()).strip()
|
||||||
|
sort = unicode(self.table.item(row, 1).text()).strip()
|
||||||
|
orig_aut,orig_sort = self.authors[id]
|
||||||
|
if orig_aut != aut or orig_sort != sort:
|
||||||
|
self.result.append((id, orig_aut, aut, sort))
|
||||||
|
|
||||||
|
def cell_changed(self, row, col):
|
||||||
|
if col == 0:
|
||||||
|
item = self.table.item(row, 0)
|
||||||
|
aut = unicode(item.text()).strip()
|
||||||
|
c = self.table.item(row, 1)
|
||||||
|
c.setText(author_to_author_sort(aut))
|
||||||
|
item = c
|
||||||
|
else:
|
||||||
|
item = self.table.item(row, 1)
|
||||||
|
self.table.setCurrentItem(item)
|
||||||
|
# disable and reenable sorting to force the sort now, so we can scroll
|
||||||
|
# to the item after it moves
|
||||||
|
self.table.setSortingEnabled(False)
|
||||||
|
self.table.setSortingEnabled(True)
|
||||||
|
self.table.scrollToItem(item)
|
86
src/calibre/gui2/dialogs/edit_authors_dialog.ui
Normal file
86
src/calibre/gui2/dialogs/edit_authors_dialog.ui
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>EditAuthorsDialog</class>
|
||||||
|
<widget class="QDialog" name="EditAuthorsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>730</width>
|
||||||
|
<height>342</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Manage authors</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="table">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="columnCount">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>EditAuthorsDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>229</x>
|
||||||
|
<y>211</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>234</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>EditAuthorsDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>297</x>
|
||||||
|
<y>217</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>234</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -8,7 +8,7 @@ from PyQt4.QtGui import QDialog, QGridLayout
|
|||||||
|
|
||||||
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, authors_to_sort_string, \
|
from calibre.ebooks.metadata import string_to_authors, \
|
||||||
authors_to_string
|
authors_to_string
|
||||||
from calibre.gui2.custom_column_widgets import populate_bulk_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_bulk_metadata_page
|
||||||
|
|
||||||
@ -110,10 +110,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
au = string_to_authors(au)
|
au = string_to_authors(au)
|
||||||
self.db.set_authors(id, au, notify=False)
|
self.db.set_authors(id, au, notify=False)
|
||||||
if self.auto_author_sort.isChecked():
|
if self.auto_author_sort.isChecked():
|
||||||
aut = self.db.authors(id, index_is_id=True)
|
x = self.db.author_sort_from_book(id, index_is_id=True)
|
||||||
aut = aut if aut else ''
|
|
||||||
aut = [a.strip().replace('|', ',') for a in aut.strip().split(',')]
|
|
||||||
x = authors_to_sort_string(aut)
|
|
||||||
if x:
|
if x:
|
||||||
self.db.set_author_sort(id, x, notify=False)
|
self.db.set_author_sort(id, x, notify=False)
|
||||||
aus = unicode(self.author_sort.text())
|
aus = unicode(self.author_sort.text())
|
||||||
|
@ -23,7 +23,7 @@ from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
|
|||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.gui2.widgets import ProgressIndicator
|
from calibre.gui2.widgets import ProgressIndicator
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.metadata import authors_to_sort_string, string_to_authors, \
|
from calibre.ebooks.metadata import string_to_authors, \
|
||||||
authors_to_string, check_isbn
|
authors_to_string, check_isbn
|
||||||
from calibre.ebooks.metadata.library_thing import cover_from_isbn
|
from calibre.ebooks.metadata.library_thing import cover_from_isbn
|
||||||
from calibre import islinux, isfreebsd
|
from calibre import islinux, isfreebsd
|
||||||
@ -460,7 +460,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
au = unicode(self.authors.text())
|
au = unicode(self.authors.text())
|
||||||
au = re.sub(r'\s+et al\.$', '', au)
|
au = re.sub(r'\s+et al\.$', '', au)
|
||||||
authors = string_to_authors(au)
|
authors = string_to_authors(au)
|
||||||
self.author_sort.setText(authors_to_sort_string(authors))
|
self.author_sort.setText(self.db.author_sort_from_authors(authors))
|
||||||
|
|
||||||
def swap_title_author(self):
|
def swap_title_author(self):
|
||||||
title = self.title.text()
|
title = self.title.text()
|
||||||
|
@ -121,6 +121,9 @@
|
|||||||
<property name="standardButtons">
|
<property name="standardButtons">
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -420,8 +420,11 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
pt.orig_file_path = os.path.abspath(src.name)
|
pt.orig_file_path = os.path.abspath(src.name)
|
||||||
pt.seek(0)
|
pt.seek(0)
|
||||||
if set_metadata:
|
if set_metadata:
|
||||||
_set_metadata(pt, self.db.get_metadata(id, get_cover=True, index_is_id=True),
|
try:
|
||||||
|
_set_metadata(pt, self.db.get_metadata(id, get_cover=True, index_is_id=True),
|
||||||
format)
|
format)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
pt.close()
|
pt.close()
|
||||||
def to_uni(x):
|
def to_uni(x):
|
||||||
if isbytestring(x):
|
if isbytestring(x):
|
||||||
|
@ -13,15 +13,76 @@ from functools import partial
|
|||||||
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \
|
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \
|
||||||
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
|
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
|
||||||
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
|
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
|
||||||
QPushButton, QWidget
|
QPushButton, QWidget, QItemDelegate, QString, QPen, \
|
||||||
|
QColor, QLinearGradient, QBrush
|
||||||
|
|
||||||
from calibre.gui2 import config, NONE
|
from calibre.gui2 import config, NONE
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.library.field_metadata import TagsIcons
|
from calibre.library.field_metadata import TagsIcons
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.dialogs.tag_categories import TagCategories
|
from calibre.gui2.dialogs.tag_categories import TagCategories
|
||||||
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
||||||
|
from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
|
||||||
|
|
||||||
|
class TagDelegate(QItemDelegate):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QItemDelegate.__init__(self, parent)
|
||||||
|
self._parent = parent
|
||||||
|
self.icon = QIcon(I('star.png'))
|
||||||
|
|
||||||
|
def paint(self, painter, option, index):
|
||||||
|
item = index.internalPointer()
|
||||||
|
if item.type != TagTreeItem.TAG:
|
||||||
|
QItemDelegate.paint(self, painter, option, index)
|
||||||
|
return
|
||||||
|
r = option.rect
|
||||||
|
# Paint the decoration icon
|
||||||
|
icon = self._parent.model().data(index, Qt.DecorationRole).toPyObject()
|
||||||
|
icon.paint(painter, r, Qt.AlignLeft)
|
||||||
|
|
||||||
|
# Paint the rating, if any. The decoration icon is assumed to be square,
|
||||||
|
# filling the row top to bottom. The three is arbitrary, there to
|
||||||
|
# provide a little space between the icon and what follows
|
||||||
|
r.setLeft(r.left()+r.height()+3)
|
||||||
|
rating = item.tag.avg_rating
|
||||||
|
if config['show_avg_rating'] and item.tag.avg_rating is not None:
|
||||||
|
painter.save()
|
||||||
|
if tweaks['render_avg_rating_using'] == 'star':
|
||||||
|
painter.setClipRect(r.left(), r.top(),
|
||||||
|
int(r.height()*(rating/5.0)), r.height())
|
||||||
|
self.icon.paint(painter, r, Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
|
r.setLeft(r.left() + r.height())
|
||||||
|
else:
|
||||||
|
painter.translate(r.left(), r.top())
|
||||||
|
# Compute factor so sizes can be expressed in percentages of the
|
||||||
|
# box defined by the row height
|
||||||
|
factor = r.height()/100.
|
||||||
|
width = 20
|
||||||
|
height = 80
|
||||||
|
left_offset = 5
|
||||||
|
top_offset = 10
|
||||||
|
if r > 0.0:
|
||||||
|
color = QColor(100, 100, 255) #medium blue, less glare
|
||||||
|
pen = QPen(color, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
||||||
|
painter.setPen(pen)
|
||||||
|
painter.scale(factor, factor)
|
||||||
|
painter.drawRect(left_offset, top_offset, width, height)
|
||||||
|
fill_height = height*(rating/5.0)
|
||||||
|
gradient = QLinearGradient(0, 0, 0, 100)
|
||||||
|
gradient.setColorAt(0.0, color)
|
||||||
|
gradient.setColorAt(1.0, color)
|
||||||
|
painter.setBrush(QBrush(gradient))
|
||||||
|
painter.drawRect(left_offset, top_offset+(height-fill_height),
|
||||||
|
width, fill_height)
|
||||||
|
# The '3' is arbitrary, there because we need a little space
|
||||||
|
# between the rectangle and the text.
|
||||||
|
r.setLeft(r.left() + ((width+left_offset*2)*factor) + 3)
|
||||||
|
painter.restore()
|
||||||
|
# Paint the text
|
||||||
|
painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
|
||||||
|
QString('[%d] %s'%(item.tag.count, item.tag.name)))
|
||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
@ -30,6 +91,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
user_category_edit = pyqtSignal(object)
|
user_category_edit = pyqtSignal(object)
|
||||||
tag_list_edit = pyqtSignal(object, object)
|
tag_list_edit = pyqtSignal(object, object)
|
||||||
saved_search_edit = pyqtSignal(object)
|
saved_search_edit = pyqtSignal(object)
|
||||||
|
author_sort_edit = pyqtSignal(object, object)
|
||||||
tag_item_renamed = pyqtSignal()
|
tag_item_renamed = pyqtSignal()
|
||||||
search_item_renamed = pyqtSignal()
|
search_item_renamed = pyqtSignal()
|
||||||
|
|
||||||
@ -43,6 +105,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.setAlternatingRowColors(True)
|
self.setAlternatingRowColors(True)
|
||||||
self.setAnimated(True)
|
self.setAnimated(True)
|
||||||
self.setHeaderHidden(True)
|
self.setHeaderHidden(True)
|
||||||
|
self.setItemDelegate(TagDelegate(self))
|
||||||
|
|
||||||
def set_database(self, db, tag_match, popularity):
|
def set_database(self, db, tag_match, popularity):
|
||||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||||
@ -112,6 +175,9 @@ class TagsView(QTreeView): # {{{
|
|||||||
if action == 'manage_searches':
|
if action == 'manage_searches':
|
||||||
self.saved_search_edit.emit(category)
|
self.saved_search_edit.emit(category)
|
||||||
return
|
return
|
||||||
|
if action == 'edit_author_sort':
|
||||||
|
self.author_sort_edit.emit(self, index)
|
||||||
|
return
|
||||||
if action == 'hide':
|
if action == 'hide':
|
||||||
self.hidden_categories.add(category)
|
self.hidden_categories.add(category)
|
||||||
elif action == 'show':
|
elif action == 'show':
|
||||||
@ -132,6 +198,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
if item.type == TagTreeItem.TAG:
|
if item.type == TagTreeItem.TAG:
|
||||||
tag_item = item
|
tag_item = item
|
||||||
tag_name = item.tag.name
|
tag_name = item.tag.name
|
||||||
|
tag_id = item.tag.id
|
||||||
item = item.parent
|
item = item.parent
|
||||||
if item.type == TagTreeItem.CATEGORY:
|
if item.type == TagTreeItem.CATEGORY:
|
||||||
category = unicode(item.name.toString())
|
category = unicode(item.name.toString())
|
||||||
@ -147,9 +214,13 @@ class TagsView(QTreeView): # {{{
|
|||||||
(key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
|
(key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
|
||||||
self.db.field_metadata[key]['is_custom'] and \
|
self.db.field_metadata[key]['is_custom'] and \
|
||||||
self.db.field_metadata[key]['datatype'] != 'rating'):
|
self.db.field_metadata[key]['datatype'] != 'rating'):
|
||||||
self.context_menu.addAction(_('Rename') + " '" + tag_name + "'",
|
self.context_menu.addAction(_('Rename \'%s\'')%tag_name,
|
||||||
partial(self.context_menu_handler, action='edit_item',
|
partial(self.context_menu_handler, action='edit_item',
|
||||||
category=tag_item, index=index))
|
category=tag_item, index=index))
|
||||||
|
if key == 'authors':
|
||||||
|
self.context_menu.addAction(_('Edit sort for \'%s\'')%tag_name,
|
||||||
|
partial(self.context_menu_handler,
|
||||||
|
action='edit_author_sort', index=tag_id))
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
# Hide/Show/Restore categories
|
# Hide/Show/Restore categories
|
||||||
self.context_menu.addAction(_('Hide category %s') % category,
|
self.context_menu.addAction(_('Hide category %s') % category,
|
||||||
@ -166,9 +237,12 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
if key in ['tags', 'publisher', 'series'] or \
|
if key in ['tags', 'publisher', 'series'] or \
|
||||||
self.db.field_metadata[key]['is_custom']:
|
self.db.field_metadata[key]['is_custom']:
|
||||||
self.context_menu.addAction(_('Manage ') + category,
|
self.context_menu.addAction(_('Manage %s')%category,
|
||||||
partial(self.context_menu_handler, action='open_editor',
|
partial(self.context_menu_handler, action='open_editor',
|
||||||
category=tag_name, key=key))
|
category=tag_name, key=key))
|
||||||
|
elif key == 'authors':
|
||||||
|
self.context_menu.addAction(_('Manage %s')%category,
|
||||||
|
partial(self.context_menu_handler, action='edit_author_sort'))
|
||||||
elif key == 'search':
|
elif key == 'search':
|
||||||
self.context_menu.addAction(_('Manage Saved Searches'),
|
self.context_menu.addAction(_('Manage Saved Searches'),
|
||||||
partial(self.context_menu_handler, action='manage_searches',
|
partial(self.context_menu_handler, action='manage_searches',
|
||||||
@ -298,7 +372,11 @@ class TagTreeItem(object): # {{{
|
|||||||
if self.tag.count == 0:
|
if self.tag.count == 0:
|
||||||
return QVariant('%s'%(self.tag.name))
|
return QVariant('%s'%(self.tag.name))
|
||||||
else:
|
else:
|
||||||
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
|
if self.tag.avg_rating is None:
|
||||||
|
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
|
||||||
|
else:
|
||||||
|
return QVariant('[%d][%3.1f] %s'%(self.tag.count,
|
||||||
|
self.tag.avg_rating, self.tag.name))
|
||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
return QVariant(self.tag.name)
|
return QVariant(self.tag.name)
|
||||||
if role == Qt.DecorationRole:
|
if role == Qt.DecorationRole:
|
||||||
@ -332,6 +410,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
':custom' : QIcon(I('column.svg')),
|
':custom' : QIcon(I('column.svg')),
|
||||||
':user' : QIcon(I('drawer.svg')),
|
':user' : QIcon(I('drawer.svg')),
|
||||||
'search' : QIcon(I('search.svg'))})
|
'search' : QIcon(I('search.svg'))})
|
||||||
|
self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
|
||||||
|
|
||||||
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||||
self.db = db
|
self.db = db
|
||||||
@ -354,7 +433,14 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
data=self.categories[i],
|
data=self.categories[i],
|
||||||
category_icon=self.category_icon_map[r],
|
category_icon=self.category_icon_map[r],
|
||||||
tooltip=tt, category_key=r)
|
tooltip=tt, category_key=r)
|
||||||
|
# This duplicates code in refresh(). Having it here as well
|
||||||
|
# can save seconds during startup, because we avoid a second
|
||||||
|
# call to get_node_tree.
|
||||||
for tag in data[r]:
|
for tag in data[r]:
|
||||||
|
if r not in self.categories_with_ratings and \
|
||||||
|
not self.db.field_metadata[r]['is_custom'] and \
|
||||||
|
not self.db.field_metadata[r]['kind'] == 'user':
|
||||||
|
tag.avg_rating = None
|
||||||
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
|
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
@ -417,6 +503,10 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
if len(data[r]) > 0:
|
if len(data[r]) > 0:
|
||||||
self.beginInsertRows(category_index, 0, len(data[r])-1)
|
self.beginInsertRows(category_index, 0, len(data[r])-1)
|
||||||
for tag in data[r]:
|
for tag in data[r]:
|
||||||
|
if r not in self.categories_with_ratings and \
|
||||||
|
not self.db.field_metadata[r]['is_custom'] and \
|
||||||
|
not self.db.field_metadata[r]['kind'] == 'user':
|
||||||
|
tag.avg_rating = None
|
||||||
tag.state = state_map.get(tag.name, 0)
|
tag.state = state_map.get(tag.name, 0)
|
||||||
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
|
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
@ -607,6 +697,7 @@ class TagBrowserMixin(object): # {{{
|
|||||||
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
||||||
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
||||||
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
||||||
|
self.tags_view.author_sort_edit.connect(self.do_author_sort_edit)
|
||||||
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
|
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
|
||||||
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
|
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
|
||||||
self.edit_categories.clicked.connect(lambda x:
|
self.edit_categories.clicked.connect(lambda x:
|
||||||
@ -636,6 +727,19 @@ class TagBrowserMixin(object): # {{{
|
|||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear_to_help()
|
||||||
self.search.clear_to_help()
|
self.search.clear_to_help()
|
||||||
|
|
||||||
|
def do_author_sort_edit(self, parent, id):
|
||||||
|
db = self.library_view.model().db
|
||||||
|
editor = EditAuthorsDialog(parent, db, id)
|
||||||
|
d = editor.exec_()
|
||||||
|
if d:
|
||||||
|
for (id, old_author, new_author, new_sort) in editor.result:
|
||||||
|
if old_author != new_author:
|
||||||
|
# The id might change if the new author already exists
|
||||||
|
id = db.rename_author(id, new_author)
|
||||||
|
db.set_sort_field_for_author(id, unicode(new_sort))
|
||||||
|
self.library_view.model().refresh()
|
||||||
|
self.tags_view.recount()
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class TagBrowserWidget(QWidget): # {{{
|
class TagBrowserWidget(QWidget): # {{{
|
||||||
|
@ -461,14 +461,27 @@ class CustomColumns(object):
|
|||||||
CREATE VIEW tag_browser_{table} AS SELECT
|
CREATE VIEW tag_browser_{table} AS SELECT
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
(SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count
|
(SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count,
|
||||||
|
(SELECT AVG(r.rating)
|
||||||
|
FROM {lt},
|
||||||
|
books_ratings_link as bl,
|
||||||
|
ratings as r
|
||||||
|
WHERE {lt}.value={table}.id and bl.book={lt}.book and
|
||||||
|
r.id = bl.rating and r.rating <> 0) avg_rating
|
||||||
FROM {table};
|
FROM {table};
|
||||||
|
|
||||||
CREATE VIEW tag_browser_filtered_{table} AS SELECT
|
CREATE VIEW tag_browser_filtered_{table} AS SELECT
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
(SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
|
(SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
|
||||||
books_list_filter(book)) count
|
books_list_filter(book)) count,
|
||||||
|
(SELECT AVG(r.rating)
|
||||||
|
FROM {lt},
|
||||||
|
books_ratings_link as bl,
|
||||||
|
ratings as r
|
||||||
|
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
|
||||||
|
r.id = bl.rating AND r.rating <> 0 AND
|
||||||
|
books_list_filter(bl.book)) avg_rating
|
||||||
FROM {table};
|
FROM {table};
|
||||||
|
|
||||||
'''.format(lt=lt, table=table),
|
'''.format(lt=lt, table=table),
|
||||||
@ -505,7 +518,6 @@ class CustomColumns(object):
|
|||||||
END;
|
END;
|
||||||
'''.format(table=table),
|
'''.format(table=table),
|
||||||
]
|
]
|
||||||
|
|
||||||
script = ' \n'.join(lines)
|
script = ' \n'.join(lines)
|
||||||
self.conn.executescript(script)
|
self.conn.executescript(script)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
@ -12,7 +12,7 @@ from math import floor
|
|||||||
|
|
||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
|
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||||
from calibre.library.schema_upgrades import SchemaUpgrade
|
from calibre.library.schema_upgrades import SchemaUpgrade
|
||||||
@ -20,7 +20,7 @@ from calibre.library.caches import ResultCache
|
|||||||
from calibre.library.custom_columns import CustomColumns
|
from calibre.library.custom_columns import CustomColumns
|
||||||
from calibre.library.sqlite import connect, IntegrityError, DBThread
|
from calibre.library.sqlite import connect, IntegrityError, DBThread
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
|
||||||
MetaInformation, authors_to_sort_string
|
MetaInformation
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
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
|
||||||
@ -56,11 +56,14 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
|||||||
|
|
||||||
class Tag(object):
|
class Tag(object):
|
||||||
|
|
||||||
def __init__(self, name, id=None, count=0, state=0, tooltip=None, icon=None):
|
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
|
||||||
|
tooltip=None, icon=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.id = id
|
self.id = id
|
||||||
self.count = count
|
self.count = count
|
||||||
self.state = state
|
self.state = state
|
||||||
|
self.avg_rating = avg/2.0 if avg is not None else 0
|
||||||
|
self.sort = sort
|
||||||
self.tooltip = tooltip
|
self.tooltip = tooltip
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
|
||||||
@ -133,7 +136,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT
|
CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count
|
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,
|
||||||
|
(0) as avg_rating,
|
||||||
|
name as sort
|
||||||
FROM tags as x WHERE name!="{0}" AND id IN
|
FROM tags as x WHERE name!="{0}" AND id IN
|
||||||
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
|
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
|
||||||
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
|
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
|
||||||
@ -144,7 +149,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT
|
CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count
|
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,
|
||||||
|
(0) as avg_rating,
|
||||||
|
name as sort
|
||||||
FROM tags as x WHERE name!="{0}" AND id IN
|
FROM tags as x WHERE name!="{0}" AND id IN
|
||||||
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
|
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
|
||||||
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
|
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
|
||||||
@ -422,6 +429,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
|
if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
|
||||||
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
|
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
|
||||||
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
|
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
|
||||||
|
mi.authors_sort_strings = self.authors_sort_strings(idx, index_is_id)
|
||||||
mi.comments = self.comments(idx, index_is_id=index_is_id)
|
mi.comments = self.comments(idx, index_is_id=index_is_id)
|
||||||
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
|
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
|
||||||
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
|
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
|
||||||
@ -698,13 +706,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
continue
|
continue
|
||||||
cn = cat['column']
|
cn = cat['column']
|
||||||
if ids is None:
|
if ids is None:
|
||||||
query = 'SELECT id, {0}, count FROM tag_browser_{1}'.format(cn, tn)
|
query = '''SELECT id, {0}, count, avg_rating, sort
|
||||||
|
FROM tag_browser_{1}'''.format(cn, tn)
|
||||||
else:
|
else:
|
||||||
query = 'SELECT id, {0}, count FROM tag_browser_filtered_{1}'.format(cn, tn)
|
query = '''SELECT id, {0}, count, avg_rating, sort
|
||||||
|
FROM tag_browser_filtered_{1}'''.format(cn, tn)
|
||||||
if sort_on_count:
|
if sort_on_count:
|
||||||
query += ' ORDER BY count DESC'
|
query += ' ORDER BY count DESC'
|
||||||
else:
|
else:
|
||||||
query += ' ORDER BY {0} ASC'.format(cn)
|
query += ' ORDER BY sort ASC'
|
||||||
data = self.conn.get(query)
|
data = self.conn.get(query)
|
||||||
|
|
||||||
# icon_map is not None if get_categories is to store an icon and
|
# icon_map is not None if get_categories is to store an icon and
|
||||||
@ -722,6 +732,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
datatype = cat['datatype']
|
datatype = cat['datatype']
|
||||||
if datatype == 'rating':
|
if datatype == 'rating':
|
||||||
|
# eliminate the zero ratings line as well as count == 0
|
||||||
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
|
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
|
||||||
formatter = (lambda x:u'\u2605'*int(round(x/2.)))
|
formatter = (lambda x:u'\u2605'*int(round(x/2.)))
|
||||||
elif category == 'authors':
|
elif category == 'authors':
|
||||||
@ -733,15 +744,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
formatter = (lambda x:unicode(x))
|
formatter = (lambda x:unicode(x))
|
||||||
|
|
||||||
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
||||||
icon=icon, tooltip = tooltip)
|
avg=r[3], sort=r[4],
|
||||||
|
icon=icon, tooltip=tooltip)
|
||||||
for r in data if item_not_zero_func(r)]
|
for r in data if item_not_zero_func(r)]
|
||||||
if category == 'series' and not sort_on_count:
|
|
||||||
if tweaks['title_series_sorting'] == 'library_order':
|
|
||||||
ts = lambda x: title_sort(x)
|
|
||||||
else:
|
|
||||||
ts = lambda x:x
|
|
||||||
categories[category].sort(cmp=lambda x,y:cmp(ts(x.name).lower(),
|
|
||||||
ts(y.name).lower()))
|
|
||||||
|
|
||||||
# We delayed computing the standard formats category because it does not
|
# We delayed computing the standard formats category because it does not
|
||||||
# use a view, but is computed dynamically
|
# use a view, but is computed dynamically
|
||||||
@ -909,6 +914,38 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
|
# Given a book, return the list of author sort strings for the book's authors
|
||||||
|
def authors_sort_strings(self, id, index_is_id=False):
|
||||||
|
id = id if index_is_id else self.id(id)
|
||||||
|
aut_strings = self.conn.get('''
|
||||||
|
SELECT sort
|
||||||
|
FROM authors, books_authors_link as bl
|
||||||
|
WHERE bl.book=? and authors.id=bl.author
|
||||||
|
ORDER BY bl.id''', (id,))
|
||||||
|
result = []
|
||||||
|
for (sort,) in aut_strings:
|
||||||
|
result.append(sort)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Given a book, return the author_sort string for authors of the book
|
||||||
|
def author_sort_from_book(self, id, index_is_id=False):
|
||||||
|
auts = self.authors_sort_strings(id, index_is_id)
|
||||||
|
return ' & '.join(auts).replace('|', ',')
|
||||||
|
|
||||||
|
# Given a list of authors, return the author_sort string for the authors,
|
||||||
|
# preferring the author sort associated with the author over the computed
|
||||||
|
# string
|
||||||
|
def author_sort_from_authors(self, authors):
|
||||||
|
result = []
|
||||||
|
for aut in authors:
|
||||||
|
r = self.conn.get('SELECT sort FROM authors WHERE name=?',
|
||||||
|
(aut.replace(',', '|'),), all=False)
|
||||||
|
if r is None:
|
||||||
|
result.append(author_to_author_sort(aut))
|
||||||
|
else:
|
||||||
|
result.append(r)
|
||||||
|
return ' & '.join(result).replace('|', ',')
|
||||||
|
|
||||||
def set_authors(self, id, authors, notify=True):
|
def set_authors(self, id, authors, notify=True):
|
||||||
'''
|
'''
|
||||||
`authors`: A list of authors.
|
`authors`: A list of authors.
|
||||||
@ -935,7 +972,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
(id, aid))
|
(id, aid))
|
||||||
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
except IntegrityError: # Sometimes books specify the same author twice in their metadata
|
||||||
pass
|
pass
|
||||||
ss = authors_to_sort_string(authors)
|
self.conn.commit()
|
||||||
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
||||||
(ss, id))
|
(ss, id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -1007,6 +1045,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def rename_tag(self, old_id, new_name):
|
def rename_tag(self, old_id, new_name):
|
||||||
|
new_name = new_name.strip()
|
||||||
new_id = self.conn.get(
|
new_id = self.conn.get(
|
||||||
'''SELECT id from tags
|
'''SELECT id from tags
|
||||||
WHERE name=?''', (new_name,), all=False)
|
WHERE name=?''', (new_name,), all=False)
|
||||||
@ -1046,6 +1085,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def rename_series(self, old_id, new_name):
|
def rename_series(self, old_id, new_name):
|
||||||
|
new_name = new_name.strip()
|
||||||
new_id = self.conn.get(
|
new_id = self.conn.get(
|
||||||
'''SELECT id from series
|
'''SELECT id from series
|
||||||
WHERE name=?''', (new_name,), all=False)
|
WHERE name=?''', (new_name,), all=False)
|
||||||
@ -1075,7 +1115,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
index = index + 1
|
index = index + 1
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
|
|
||||||
def delete_series_using_id(self, id):
|
def delete_series_using_id(self, id):
|
||||||
books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,))
|
books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,))
|
||||||
self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,))
|
self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,))
|
||||||
@ -1091,6 +1130,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def rename_publisher(self, old_id, new_name):
|
def rename_publisher(self, old_id, new_name):
|
||||||
|
new_name = new_name.strip()
|
||||||
new_id = self.conn.get(
|
new_id = self.conn.get(
|
||||||
'''SELECT id from publishers
|
'''SELECT id from publishers
|
||||||
WHERE name=?''', (new_name,), all=False)
|
WHERE name=?''', (new_name,), all=False)
|
||||||
@ -1113,12 +1153,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,))
|
self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
# There is no editor for author, so we do not need get_authors_with_ids or
|
def get_authors_with_ids(self):
|
||||||
# delete_author_using_id.
|
result = self.conn.get('SELECT id,name,sort FROM authors')
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result
|
||||||
|
|
||||||
|
def set_sort_field_for_author(self, old_id, new_sort):
|
||||||
|
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
|
||||||
|
(new_sort.strip(), old_id))
|
||||||
|
self.conn.commit()
|
||||||
|
# Now change all the author_sort fields in books by this author
|
||||||
|
bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,))
|
||||||
|
for (book_id,) in bks:
|
||||||
|
ss = self.author_sort_from_book(book_id, index_is_id=True)
|
||||||
|
self.set_author_sort(book_id, ss)
|
||||||
|
|
||||||
def rename_author(self, old_id, new_name):
|
def rename_author(self, old_id, new_name):
|
||||||
# Make sure that any commas in new_name are changed to '|'!
|
# Make sure that any commas in new_name are changed to '|'!
|
||||||
new_name = new_name.replace(',', '|')
|
new_name = new_name.replace(',', '|').strip()
|
||||||
|
|
||||||
# Get the list of books we must fix up, one way or the other
|
# Get the list of books we must fix up, one way or the other
|
||||||
# Save the list so we can use it twice
|
# Save the list so we can use it twice
|
||||||
@ -1141,7 +1194,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.conn.execute('UPDATE authors SET name=? WHERE id=?',
|
self.conn.execute('UPDATE authors SET name=? WHERE id=?',
|
||||||
(new_name, old_id))
|
(new_name, old_id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return
|
return new_id
|
||||||
# Author exists. To fix this, we must replace all the authors
|
# Author exists. To fix this, we must replace all the authors
|
||||||
# instead of replacing the one. Reason: db integrity checks can stop
|
# instead of replacing the one. Reason: db integrity checks can stop
|
||||||
# the rename process, which would leave everything half-done. We
|
# the rename process, which would leave everything half-done. We
|
||||||
@ -1184,24 +1237,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# now fix the filesystem paths
|
# now fix the filesystem paths
|
||||||
self.set_path(book_id, index_is_id=True)
|
self.set_path(book_id, index_is_id=True)
|
||||||
# Next fix the author sort. Reset it to the default
|
# Next fix the author sort. Reset it to the default
|
||||||
authors = self.conn.get('''
|
ss = self.author_sort_from_book(book_id, index_is_id=True)
|
||||||
SELECT authors.name
|
self.set_author_sort(book_id, ss)
|
||||||
FROM authors, books_authors_link as bl
|
|
||||||
WHERE bl.book = ? and bl.author = authors.id
|
|
||||||
ORDER BY bl.id
|
|
||||||
''' , (book_id,))
|
|
||||||
# unpack the double-list structure
|
|
||||||
for i,aut in enumerate(authors):
|
|
||||||
authors[i] = aut[0]
|
|
||||||
ss = authors_to_sort_string(authors)
|
|
||||||
# Change the '|'s to ','
|
|
||||||
ss = ss.replace('|', ',')
|
|
||||||
self.conn.execute('''UPDATE books
|
|
||||||
SET author_sort=?
|
|
||||||
WHERE id=?''', (ss, book_id))
|
|
||||||
self.conn.commit()
|
|
||||||
# the caller will do a general refresh, so we don't need to
|
# the caller will do a general refresh, so we don't need to
|
||||||
# do one here
|
# do one here
|
||||||
|
return new_id
|
||||||
|
|
||||||
# end convenience methods
|
# end convenience methods
|
||||||
|
|
||||||
@ -1436,7 +1476,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if not add_duplicates and self.has_book(mi):
|
if not add_duplicates and self.has_book(mi):
|
||||||
return None
|
return None
|
||||||
series_index = 1.0 if mi.series_index is None else mi.series_index
|
series_index = 1.0 if mi.series_index is None else mi.series_index
|
||||||
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
|
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
|
||||||
title = mi.title
|
title = mi.title
|
||||||
if isinstance(aus, str):
|
if isinstance(aus, str):
|
||||||
aus = aus.decode(preferred_encoding, 'replace')
|
aus = aus.decode(preferred_encoding, 'replace')
|
||||||
@ -1476,7 +1516,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
duplicates.append((path, format, mi))
|
duplicates.append((path, format, mi))
|
||||||
continue
|
continue
|
||||||
series_index = 1.0 if mi.series_index is None else mi.series_index
|
series_index = 1.0 if mi.series_index is None else mi.series_index
|
||||||
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
|
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
|
||||||
title = mi.title
|
title = mi.title
|
||||||
if isinstance(aus, str):
|
if isinstance(aus, str):
|
||||||
aus = aus.decode(preferred_encoding, 'replace')
|
aus = aus.decode(preferred_encoding, 'replace')
|
||||||
@ -1515,7 +1555,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
mi.title = _('Unknown')
|
mi.title = _('Unknown')
|
||||||
if not mi.authors:
|
if not mi.authors:
|
||||||
mi.authors = [_('Unknown')]
|
mi.authors = [_('Unknown')]
|
||||||
aus = mi.author_sort if mi.author_sort else authors_to_sort_string(mi.authors)
|
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
|
||||||
if isinstance(aus, str):
|
if isinstance(aus, str):
|
||||||
aus = aus.decode(preferred_encoding, 'replace')
|
aus = aus.decode(preferred_encoding, 'replace')
|
||||||
title = mi.title if isinstance(mi.title, unicode) else \
|
title = mi.title if isinstance(mi.title, unicode) else \
|
||||||
|
@ -44,9 +44,12 @@ class FieldMetadata(dict):
|
|||||||
is_category: is a tag browser category. If true, then:
|
is_category: is a tag browser category. If true, then:
|
||||||
table: name of the db table used to construct item list
|
table: name of the db table used to construct item list
|
||||||
column: name of the column in the normalized table to join on
|
column: name of the column in the normalized table to join on
|
||||||
link_column: name of the column in the connection table to join on
|
link_column: name of the column in the connection table to join on. This
|
||||||
|
key should not be present if there is no link table
|
||||||
|
category_sort: the field in the normalized table to sort on. This
|
||||||
|
key must be present if is_category is True
|
||||||
If these are None, then the category constructor must know how
|
If these are None, then the category constructor must know how
|
||||||
to build the item list (e.g., formats).
|
to build the item list (e.g., formats, news).
|
||||||
The order below is the order that the categories will
|
The order below is the order that the categories will
|
||||||
appear in the tags pane.
|
appear in the tags pane.
|
||||||
|
|
||||||
@ -66,6 +69,7 @@ class FieldMetadata(dict):
|
|||||||
('authors', {'table':'authors',
|
('authors', {'table':'authors',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column':'author',
|
'link_column':'author',
|
||||||
|
'category_sort':'sort',
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
'is_multiple':',',
|
'is_multiple':',',
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
@ -76,6 +80,7 @@ class FieldMetadata(dict):
|
|||||||
('series', {'table':'series',
|
('series', {'table':'series',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column':'series',
|
'link_column':'series',
|
||||||
|
'category_sort':'(title_sort(name))',
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
'is_multiple':None,
|
'is_multiple':None,
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
@ -95,6 +100,7 @@ class FieldMetadata(dict):
|
|||||||
('publisher', {'table':'publishers',
|
('publisher', {'table':'publishers',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column':'publisher',
|
'link_column':'publisher',
|
||||||
|
'category_sort':'name',
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
'is_multiple':None,
|
'is_multiple':None,
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
@ -105,6 +111,7 @@ class FieldMetadata(dict):
|
|||||||
('rating', {'table':'ratings',
|
('rating', {'table':'ratings',
|
||||||
'column':'rating',
|
'column':'rating',
|
||||||
'link_column':'rating',
|
'link_column':'rating',
|
||||||
|
'category_sort':'rating',
|
||||||
'datatype':'rating',
|
'datatype':'rating',
|
||||||
'is_multiple':None,
|
'is_multiple':None,
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
@ -114,6 +121,7 @@ class FieldMetadata(dict):
|
|||||||
'is_category':True}),
|
'is_category':True}),
|
||||||
('news', {'table':'news',
|
('news', {'table':'news',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
|
'category_sort':'name',
|
||||||
'datatype':None,
|
'datatype':None,
|
||||||
'is_multiple':None,
|
'is_multiple':None,
|
||||||
'kind':'category',
|
'kind':'category',
|
||||||
@ -124,6 +132,7 @@ class FieldMetadata(dict):
|
|||||||
('tags', {'table':'tags',
|
('tags', {'table':'tags',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column': 'tag',
|
'link_column': 'tag',
|
||||||
|
'category_sort':'name',
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
'is_multiple':',',
|
'is_multiple':',',
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
@ -374,7 +383,7 @@ class FieldMetadata(dict):
|
|||||||
'search_terms':[key], 'label':label,
|
'search_terms':[key], 'label':label,
|
||||||
'colnum':colnum, 'display':display,
|
'colnum':colnum, 'display':display,
|
||||||
'is_custom':True, 'is_category':is_category,
|
'is_custom':True, 'is_category':is_category,
|
||||||
'link_column':'value',
|
'link_column':'value','category_sort':'value',
|
||||||
'is_editable': is_editable,}
|
'is_editable': is_editable,}
|
||||||
self._add_search_terms_to_map(key, [key])
|
self._add_search_terms_to_map(key, [key])
|
||||||
self.custom_label_to_key_map[label] = key
|
self.custom_label_to_key_map[label] = key
|
||||||
|
@ -296,3 +296,117 @@ class SchemaUpgrade(object):
|
|||||||
('books_%s_link'%field['table'],), all=False)
|
('books_%s_link'%field['table'],), all=False)
|
||||||
if table is not None:
|
if table is not None:
|
||||||
create_tag_browser_view(field['table'], field['link_column'], field['column'])
|
create_tag_browser_view(field['table'], field['link_column'], field['column'])
|
||||||
|
|
||||||
|
def upgrade_version_11(self):
|
||||||
|
'Add average rating to tag browser views'
|
||||||
|
def create_std_tag_browser_view(table_name, column_name,
|
||||||
|
view_column_name, sort_column_name):
|
||||||
|
script = ('''
|
||||||
|
DROP VIEW IF EXISTS tag_browser_{tn};
|
||||||
|
CREATE VIEW tag_browser_{tn} AS SELECT
|
||||||
|
id,
|
||||||
|
{vcn},
|
||||||
|
(SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count,
|
||||||
|
(SELECT AVG(ratings.rating)
|
||||||
|
FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings
|
||||||
|
WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND
|
||||||
|
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
|
||||||
|
{scn} AS sort
|
||||||
|
FROM {tn};
|
||||||
|
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
|
||||||
|
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
|
||||||
|
id,
|
||||||
|
{vcn},
|
||||||
|
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
|
||||||
|
{cn}={tn}.id AND books_list_filter(book)) count,
|
||||||
|
(SELECT AVG(ratings.rating)
|
||||||
|
FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings
|
||||||
|
WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND
|
||||||
|
ratings.id = bl.rating AND ratings.rating <> 0 AND
|
||||||
|
books_list_filter(bl.book)) avg_rating,
|
||||||
|
{scn} AS sort
|
||||||
|
FROM {tn};
|
||||||
|
|
||||||
|
'''.format(tn=table_name, cn=column_name,
|
||||||
|
vcn=view_column_name, scn= sort_column_name))
|
||||||
|
self.conn.executescript(script)
|
||||||
|
|
||||||
|
def create_cust_tag_browser_view(table_name, link_table_name):
|
||||||
|
script = '''
|
||||||
|
DROP VIEW IF EXISTS tag_browser_{table};
|
||||||
|
CREATE VIEW tag_browser_{table} AS SELECT
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
(SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count,
|
||||||
|
(SELECT AVG(r.rating)
|
||||||
|
FROM {lt},
|
||||||
|
books_ratings_link AS bl,
|
||||||
|
ratings AS r
|
||||||
|
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
|
||||||
|
r.id = bl.rating AND r.rating <> 0) avg_rating,
|
||||||
|
value AS sort
|
||||||
|
FROM {table};
|
||||||
|
|
||||||
|
DROP VIEW IF EXISTS tag_browser_filtered_{table};
|
||||||
|
CREATE VIEW tag_browser_filtered_{table} AS SELECT
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
(SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
|
||||||
|
books_list_filter(book)) count,
|
||||||
|
(SELECT AVG(r.rating)
|
||||||
|
FROM {lt},
|
||||||
|
books_ratings_link AS bl,
|
||||||
|
ratings AS r
|
||||||
|
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
|
||||||
|
r.id = bl.rating AND r.rating <> 0 AND
|
||||||
|
books_list_filter(bl.book)) avg_rating,
|
||||||
|
value AS sort
|
||||||
|
FROM {table};
|
||||||
|
'''.format(lt=link_table_name, table=table_name)
|
||||||
|
self.conn.executescript(script)
|
||||||
|
|
||||||
|
for field in self.field_metadata.itervalues():
|
||||||
|
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
|
||||||
|
table = self.conn.get(
|
||||||
|
'SELECT name FROM sqlite_master WHERE type="table" AND name=?',
|
||||||
|
('books_%s_link'%field['table'],), all=False)
|
||||||
|
if table is not None:
|
||||||
|
create_std_tag_browser_view(field['table'], field['link_column'],
|
||||||
|
field['column'], field['category_sort'])
|
||||||
|
|
||||||
|
db_tables = self.conn.get('''SELECT name FROM sqlite_master
|
||||||
|
WHERE type='table'
|
||||||
|
ORDER BY name''');
|
||||||
|
tables = []
|
||||||
|
for (table,) in db_tables:
|
||||||
|
tables.append(table)
|
||||||
|
for table in tables:
|
||||||
|
link_table = 'books_%s_link'%table
|
||||||
|
if table.startswith('custom_column_') and link_table in tables:
|
||||||
|
create_cust_tag_browser_view(table, link_table)
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import author_to_author_sort
|
||||||
|
|
||||||
|
aut = self.conn.get('SELECT id, name FROM authors');
|
||||||
|
records = []
|
||||||
|
for (id, author) in aut:
|
||||||
|
records.append((id, author.replace('|', ',')))
|
||||||
|
for id,author in records:
|
||||||
|
self.conn.execute('UPDATE authors SET sort=? WHERE id=?',
|
||||||
|
(author_to_author_sort(author.replace('|', ',')).strip(), id))
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.executescript('''
|
||||||
|
DROP TRIGGER IF EXISTS author_insert_trg;
|
||||||
|
CREATE TRIGGER author_insert_trg
|
||||||
|
AFTER INSERT ON authors
|
||||||
|
BEGIN
|
||||||
|
UPDATE authors SET sort=author_to_author_sort(NEW.name) WHERE id=NEW.id;
|
||||||
|
END;
|
||||||
|
DROP TRIGGER IF EXISTS author_update_trg;
|
||||||
|
CREATE TRIGGER author_update_trg
|
||||||
|
BEFORE UPDATE ON authors
|
||||||
|
BEGIN
|
||||||
|
UPDATE authors SET sort=author_to_author_sort(NEW.name)
|
||||||
|
WHERE id=NEW.id AND name <> NEW.name;
|
||||||
|
END;
|
||||||
|
''')
|
||||||
|
@ -14,7 +14,7 @@ from Queue import Queue
|
|||||||
from threading import RLock
|
from threading import RLock
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from calibre.ebooks.metadata import title_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
|
||||||
|
|
||||||
@ -116,10 +116,12 @@ class DBThread(Thread):
|
|||||||
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)
|
||||||
if tweaks['title_series_sorting'] == 'library_order':
|
if tweaks['title_series_sorting'] == 'strictly_alphabetic':
|
||||||
self.conn.create_function('title_sort', 1, title_sort)
|
|
||||||
else:
|
|
||||||
self.conn.create_function('title_sort', 1, lambda x:x)
|
self.conn.create_function('title_sort', 1, lambda x:x)
|
||||||
|
else:
|
||||||
|
self.conn.create_function('title_sort', 1, title_sort)
|
||||||
|
self.conn.create_function('author_to_author_sort', 1,
|
||||||
|
lambda x: author_to_author_sort(x.replace('|', ',')))
|
||||||
self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4()))
|
self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4()))
|
||||||
# Dummy functions for dynamically created filters
|
# Dummy functions for dynamically created filters
|
||||||
self.conn.create_function('books_list_filter', 1, lambda x: 1)
|
self.conn.create_function('books_list_filter', 1, lambda x: 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user