diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 081112444a..750af9efa7 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -12,13 +12,24 @@ defaults. # The algorithm used to assign a new book in an existing series a series number. +# New series numbers assigned using this tweak are always integer values, except +# if a constant non-integer is specified. # Possible values are: -# next - Next available number +# next - First available integer larger than the largest existing number +# first_free - First available integer larger than 0 +# next_free - First available integer larger than the smallest existing number +# last_free - First available integer smaller than the largest existing number +# Return largest existing + 1 if no free number is found # const - Assign the number 1 always +# a number - Assign that number always. The number is not in quotes. Note that +# 0.0 can be used here. +# Examples: +# series_index_auto_increment = 'next' +# series_index_auto_increment = 'next_free' +# series_index_auto_increment = 16.5 series_index_auto_increment = 'next' - # The algorithm used to copy author to author_sort # Possible values are: # invert: use "fn ln" -> "ln, fn" (the original algorithm) @@ -235,3 +246,9 @@ doubleclick_on_library_view = 'open_viewer' # Example: locale_for_sorting = 'fr' -- sort using French rules. # Example: locale_for_sorting = 'nb' -- sort using Norwegian rules. locale_for_sorting = '' + + +# Set whether to use one or two columns for custom metadata when editing +# metadata one book at a time. If True, then the fields are laid out using two +# columns. If False, one column is used. +metadata_single_use_2_cols_for_custom_fields = True \ No newline at end of file diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 5ab8bb6940..6e4fc0a0ac 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -303,7 +303,7 @@ class Series(Base): if val == '': val = s_index = None elif s_index == 0.0: - if tweaks['series_index_auto_increment'] == 'next': + if tweaks['series_index_auto_increment'] != 'const': s_index = self.db.get_next_cc_series_num_for(val, num=self.col_id) else: @@ -572,7 +572,6 @@ class BulkSeries(BulkBase): val = None if clear else self.normalize_ui_val(val) if clear or val != '': extras = [] - next_index = self.db.get_next_cc_series_num_for(val, num=self.col_id) for book_id in book_ids: if clear: extras.append(None) @@ -581,9 +580,8 @@ class BulkSeries(BulkBase): if force_start: s_index = at_value at_value += 1 - elif tweaks['series_index_auto_increment'] == 'next': - s_index = next_index - next_index += 1 + elif tweaks['series_index_auto_increment'] != 'const': + s_index = self.db.get_next_cc_series_num_for(val, num=self.col_id) else: s_index = 1.0 else: diff --git a/src/calibre/gui2/dialogs/choose_library.py b/src/calibre/gui2/dialogs/choose_library.py index 32d45c6043..033b038b65 100644 --- a/src/calibre/gui2/dialogs/choose_library.py +++ b/src/calibre/gui2/dialogs/choose_library.py @@ -32,6 +32,11 @@ class ChooseLibrary(QDialog, Ui_Dialog): loc = unicode(self.old_location.text()).format(lp) self.old_location.setText(loc) self.browse_button.clicked.connect(self.choose_loc) + self.empty_library.toggled.connect(self.empty_library_toggled) + self.copy_structure.setEnabled(False) + + def empty_library_toggled(self, to_what): + self.copy_structure.setEnabled(to_what) def choose_loc(self, *args): loc = choose_dir(self, 'choose library location', @@ -64,7 +69,7 @@ class ChooseLibrary(QDialog, Ui_Dialog): def perform_action(self, ac, loc): if ac in ('new', 'existing'): prefs['library_path'] = loc - self.callback(loc) + self.callback(loc, copy_structure=self.copy_structure.isChecked()) else: move_library(self.db.library_path, loc, self.parent(), self.callback) diff --git a/src/calibre/gui2/dialogs/choose_library.ui b/src/calibre/gui2/dialogs/choose_library.ui index 6cf1b4386c..c281ecc0f4 100644 --- a/src/calibre/gui2/dialogs/choose_library.ui +++ b/src/calibre/gui2/dialogs/choose_library.ui @@ -49,11 +49,26 @@ - - - &Create an empty library at the new location - - + + + + + &Create an empty library at the new location + + + + + + + &Copy structure from the current library + + + Copy the custom columns, saved searches, column widths, plugboards, +user categories, and other information from the old to the new library + + + + diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 4a9bb784c8..7c5a9f95d4 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -23,7 +23,7 @@ from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.widgets import ProgressIndicator from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata import string_to_authors, \ - authors_to_string, check_isbn + authors_to_string, check_isbn, title_sort from calibre.ebooks.metadata.covers import download_cover from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata import MetaInformation @@ -444,13 +444,24 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.cover_fetcher = None self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter) base = unicode(self.author_sort.toolTip()) - self.ok_aus_tooltip = '

' + textwrap.fill(base+'

'+ + ok_tooltip = '

' + textwrap.fill(base+'

'+ _(' The green color indicates that the current ' 'author sort matches the current author')) - self.bad_aus_tooltip = '

'+textwrap.fill(base + '

'+ + bad_tooltip = '

'+textwrap.fill(base + '

'+ _(' The red color indicates that the current ' - 'author sort does not match the current author')) + 'author sort does not match the current author. ' + 'No action is required if this is what you want.')) + self.aus_tooltips = (ok_tooltip, bad_tooltip) + base = unicode(self.title_sort.toolTip()) + ok_tooltip = '

' + textwrap.fill(base+'

'+ + _(' The green color indicates that the current ' + 'title sort matches the current title')) + bad_tooltip = '

'+textwrap.fill(base + '

'+ + _(' The red color warns that the current ' + 'title sort does not match the current title. ' + 'No action is required if this is what you want.')) + self.ts_tooltips = (ok_tooltip, bad_tooltip) self.row_delta = 0 if prev: self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'), @@ -506,7 +517,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.remove_unused_series) QObject.connect(self.auto_author_sort, SIGNAL('clicked()'), self.deduce_author_sort) + QObject.connect(self.auto_title_sort, SIGNAL('clicked()'), + self.deduce_title_sort) self.trim_cover_button.clicked.connect(self.trim_cover) + self.connect(self.title_sort, SIGNAL('textChanged(const QString&)'), + self.title_sort_box_changed) + self.connect(self.title, SIGNAL('textChanged(const QString&)'), + self.title_box_changed) self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'), self.author_sort_box_changed) self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'), @@ -523,6 +540,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.title.setText(db.title(row)) + self.title_sort.setText(db.title_sort(row)) isbn = db.isbn(self.id, index_is_id=True) if not isbn: isbn = '' @@ -598,8 +616,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): w = self.central_widget.widget(1) layout = w.layout() self.custom_column_widgets, self.__cc_spacers = \ - populate_metadata_page(layout, self.db, self.id, - parent=w, bulk=False, two_column=True) + populate_metadata_page(layout, self.db, self.id, parent=w, bulk=False, + two_column=tweaks['metadata_single_use_2_cols_for_custom_fields']) self.__custom_col_layouts = [layout] ans = self.custom_column_widgets for i in range(len(ans)-1): @@ -610,27 +628,40 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): for c in range(2, len(ans[i].widgets), 2): w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1]) + def title_box_changed(self, txt): + ts = unicode(txt) + ts = title_sort(ts) + self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips, + normal=(unicode(self.title_sort.text()) == ts)) + + def title_sort_box_changed(self, txt): + ts = unicode(txt) + self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips, + normal=(title_sort(unicode(self.title.text())) == ts)) + def authors_box_changed(self, txt): aus = unicode(txt) aus = re.sub(r'\s+et al\.$', '', aus) aus = self.db.author_sort_from_authors(string_to_authors(aus)) - self.mark_author_sort(normal=(unicode(self.author_sort.text()) == aus)) + self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips, + normal=(unicode(self.author_sort.text()) == aus)) def author_sort_box_changed(self, txt): au = unicode(self.authors.text()) au = re.sub(r'\s+et al\.$', '', au) au = self.db.author_sort_from_authors(string_to_authors(au)) - self.mark_author_sort(normal=(au == txt)) + self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips, + normal=(au == txt)) - def mark_author_sort(self, normal=True): + def mark_box_as_ok(self, control, tt, normal=True): if normal: col = 'rgb(0, 255, 0, 20%)' else: col = 'rgb(255, 0, 0, 20%)' - self.author_sort.setStyleSheet('QLineEdit { color: black; ' - 'background-color: %s; }'%col) - tt = self.ok_aus_tooltip if normal else self.bad_aus_tooltip - self.author_sort.setToolTip(tt) + control.setStyleSheet('QLineEdit { color: black; ' + 'background-color: %s; }'%col) + tt = tt[0] if normal else tt[1] + control.setToolTip(tt) def validate_isbn(self, isbn): isbn = unicode(isbn).strip() @@ -652,12 +683,16 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): authors = string_to_authors(au) self.author_sort.setText(self.db.author_sort_from_authors(authors)) + def deduce_title_sort(self): + ts = unicode(self.title.text()) + self.title_sort.setText(title_sort(ts)) + def swap_title_author(self): title = self.title.text() self.title.setText(self.authors.text()) self.authors.setText(title) - self.author_sort.setText('') - + self.deduce_author_sort() + self.deduce_title_sort() def initialize_combos(self): self.initalize_authors() @@ -804,7 +839,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): series = unicode(self.series.text()).strip() if series and series != self.original_series_name: ns = 1 - if tweaks['series_index_auto_increment'] == 'next': + if tweaks['series_index_auto_increment'] != 'const': ns = self.db.get_next_series_num_for(series) self.series_index.setValue(ns) self.original_series_name = series @@ -838,6 +873,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): title = unicode(self.title.text()).strip() if title != self.original_title: self.db.set_title(self.id, title, notify=False) + # This must be after setting the title because of the DB update trigger + ts = unicode(self.title_sort.text()).strip() + if ts: + self.db.set_title_sort(self.id, ts, notify=False, commit=False) au = unicode(self.authors.text()).strip() if au and au != self.original_author: self.db.set_authors(self.id, string_to_authors(au), notify=False) diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 2bd85e30bb..770807b2d7 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -100,27 +100,27 @@ - - - - Swap the author and title - + + - ... + Title &sort: - - - :/images/swap.png:/images/swap.png + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 16 - 16 - + + title_sort - + + + + Specify how this book should be sorted when by title. For example, The Exorcist might be sorted as Exorcist, The. + + + + &Author(s): @@ -133,7 +133,14 @@ - + + + + true + + + + Author S&ort: @@ -146,34 +153,15 @@ - - - - - - Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles. + + + + Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles. If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match. - - - - - - - Automatically create the author sort entry based on the current author entry. -Using this button to create author sort will change author sort from red to green. - - - ... - - - - :/images/auto_author_sort.png:/images/auto_author_sort.png - - - - + + - + &Rating: @@ -186,7 +174,7 @@ Using this button to create author sort will change author sort from red to gree - + Rating of this book. 0-5 stars @@ -205,7 +193,7 @@ Using this button to create author sort will change author sort from red to gree - + &Publisher: @@ -218,7 +206,14 @@ Using this button to create author sort will change author sort from red to gree - + + + + true + + + + Ta&gs: @@ -231,32 +226,7 @@ Using this button to create author sort will change author sort from red to gree - - - - - - Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. - - - - - - - Open Tag Editor - - - Open Tag Editor - - - - :/images/chapters.png:/images/chapters.png - - - - - - + &Series: @@ -272,7 +242,7 @@ Using this button to create author sort will change author sort from red to gree - + 5 @@ -293,59 +263,9 @@ Using this button to create author sort will change author sort from red to gree - - - - Remove unused series (Series that have no books) - - - ... - - - - :/images/trash.png:/images/trash.png - - - - - - - IS&BN: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - isbn - - - - - - - - - Publishe&d: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - pubdate - - - - - - - true - - - - false @@ -358,34 +278,23 @@ Using this button to create author sort will change author sort from red to gree - - - - MMM yyyy + + + + IS&BN: - - true + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - true + + isbn - - - dd MMM yyyy - - - true - - + - + &Date: @@ -398,9 +307,194 @@ Using this button to create author sort will change author sort from red to gree + + + + dd MMM yyyy + + + true + + + + + + + Publishe&d: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + pubdate + + + + + + + MMM yyyy + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Automatically create the author sort entry based on the current author entry. +Using this button to create author sort will change author sort from red to green. + + + ... + + + + :/images/auto_author_sort.png:/images/auto_author_sort.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Swap the author and title + + + ... + + + + :/images/swap.png:/images/swap.png + + + + 16 + 16 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Automatically create the title sort entry based on the current title entry. +Using this button to create title sort will change title sort from red to green. + + + ... + + + + :/images/auto_author_sort.png:/images/auto_author_sort.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. + + + + + + + + + Open Tag Editor + + + Open Tag Editor + + + + :/images/chapters.png:/images/chapters.png + + + + + + + Remove unused series (Series that have no books) + + + ... + + + + :/images/trash.png:/images/trash.png + + + + + + + &Fetch metadata from server + + + @@ -420,13 +514,6 @@ Using this button to create author sort will change author sort from red to gree - - - - &Fetch metadata from server - - - @@ -744,10 +831,12 @@ Using this button to create author sort will change author sort from red to gree title + auto_title_sort + title_sort swap_button authors - author_sort auto_author_sort + author_sort rating publisher tags @@ -758,20 +847,22 @@ Using this button to create author sort will change author sort from red to gree isbn date pubdate - comments fetch_metadata_button - add_format_button - remove_format_button + comments button_set_cover button_set_metadata formats + add_format_button + remove_format_button cover_path + cover_button + trim_cover_button reset_cover fetch_cover_button generate_cover_button + button_box scrollArea central_widget - button_box diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index e0d5c64961..661f21e53d 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -772,7 +772,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.db.set_series_index(id, float(match.group(1))) val = pat.sub('', val).strip() elif val: - if tweaks['series_index_auto_increment'] == 'next': + if tweaks['series_index_auto_increment'] != 'const': ni = self.db.get_next_series_num_for(val) if ni != 1: self.db.set_series_index(id, ni) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index dd3925e732..f97589e466 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -378,13 +378,16 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def booklists(self): return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db - def library_moved(self, newloc): + def library_moved(self, newloc, copy_structure=False): if newloc is None: return + default_prefs = None try: olddb = self.library_view.model().db + if copy_structure: + default_prefs = olddb.prefs except: olddb = None - db = LibraryDatabase2(newloc) + db = LibraryDatabase2(newloc, default_prefs=default_prefs) if self.content_server is not None: self.content_server.set_database(db) self.library_path = newloc diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 01e8ad449b..c98b3be4d3 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -707,7 +707,7 @@ def parse_series_string(db, label, value): val = pat.sub('', val).strip() s_index = float(match.group(1)) elif val: - if tweaks['series_index_auto_increment'] == 'next': + if tweaks['series_index_auto_increment'] != 'const': s_index = db.get_next_cc_series_num_for(val, label=label) else: s_index = 1.0 diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index f544a9bf7e..e95ace2cd4 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -8,12 +8,12 @@ __docformat__ = 'restructuredtext en' import json, re from functools import partial -from math import floor from calibre import prints from calibre.constants import preferred_encoding from calibre.library.field_metadata import FieldMetadata from calibre.utils.date import parse_date +from calibre.utils.config import tweaks class CustomColumns(object): @@ -261,15 +261,15 @@ class CustomColumns(object): series_id = self.conn.get('SELECT id from %s WHERE value=?'%table, (series,), all=False) if series_id is None: + if isinstance(tweaks['series_index_auto_increment'], (int, float)): + return float(tweaks['series_index_auto_increment']) return 1.0 - # get the label of the associated series number table - series_num = self.conn.get(''' - SELECT MAX({lt}.extra) FROM {lt} + series_indices = self.conn.get(''' + SELECT {lt}.extra FROM {lt} WHERE {lt}.book IN (SELECT book FROM {lt} where value=?) - '''.format(lt=lt), (series_id,), all=False) - if series_num is None: - return 1.0 - return floor(series_num+1) + ORDER BY {lt}.extra + '''.format(lt=lt), (series_id,)) + return self._get_next_series_num_for_list(series_indices) def all_custom(self, label=None, num=None): if label is not None: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 23375995ae..1557f5065e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -8,7 +8,7 @@ The database used to store ebook metadata ''' import os, sys, shutil, cStringIO, glob, time, functools, traceback, re from itertools import repeat -from math import floor +from math import ceil from Queue import Queue from PyQt4.QtGui import QImage @@ -113,7 +113,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def exists_at(cls, path): return path and os.path.exists(os.path.join(path, 'metadata.db')) - def __init__(self, library_path, row_factory=False): + def __init__(self, library_path, row_factory=False, default_prefs=None): self.field_metadata = FieldMetadata() self.dirtied_queue = Queue() if not os.path.exists(library_path): @@ -127,10 +127,29 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if isinstance(self.dbpath, unicode) and not iswindows: self.dbpath = self.dbpath.encode(filesystem_encoding) + apply_default_prefs = not os.path.exists(self.dbpath) self.connect() + self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) SchemaUpgrade.__init__(self) + + # if we are to copy the prefs and structure from some other DB, then + # we need to do it before we call initialize_dynamic + if apply_default_prefs and default_prefs is not None: + dbprefs = DBPrefs(self) + for key in default_prefs: + # be sure that prefs not to be copied are listed below + if key in ['news_to_be_synced']: + continue + try: + dbprefs[key] = default_prefs[key] + except: + pass # ignore options that don't exist anymore + fmvals = [f for f in default_prefs['field_metadata'].values() if f['is_custom']] + for f in fmvals: + self.create_custom_column(f['label'], f['name'], f['datatype'], + f['is_multiple'] is not None, f['is_editable'], f['display']) self.initialize_dynamic() def get_property(self, idx, index_is_id=False, loc=-1): @@ -1365,14 +1384,42 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): series_id = self.conn.get('SELECT id from series WHERE name=?', (series,), all=False) if series_id is None: + if isinstance(tweaks['series_index_auto_increment'], (int, float)): + return float(tweaks['series_index_auto_increment']) return 1.0 - series_num = self.conn.get( - ('SELECT MAX(series_index) FROM books WHERE id IN ' - '(SELECT book FROM books_series_link where series=?)'), - (series_id,), all=False) - if series_num is None: + series_indices = self.conn.get( + ('SELECT series_index FROM books WHERE id IN ' + '(SELECT book FROM books_series_link where series=?) ' + 'ORDER BY series_index'), + (series_id,)) + return self._get_next_series_num_for_list(series_indices) + + def _get_next_series_num_for_list(self, series_indices): + if not series_indices: + if isinstance(tweaks['series_index_auto_increment'], (int, float)): + return float(tweaks['series_index_auto_increment']) return 1.0 - return floor(series_num+1) + series_indices = [x[0] for x in series_indices] + if tweaks['series_index_auto_increment'] == 'next': + return series_indices[-1] + 1 + if tweaks['series_index_auto_increment'] == 'first_free': + for i in range(1, 10000): + if i not in series_indices: + return i + # really shouldn't get here. + if tweaks['series_index_auto_increment'] == 'next_free': + for i in range(int(ceil(series_indices[0])), 10000): + if i not in series_indices: + return i + # really shouldn't get here. + if tweaks['series_index_auto_increment'] == 'last_free': + for i in range(int(ceil(series_indices[-1])), 0, -1): + if i not in series_indices: + return i + return series_indices[-1] + 1 + if isinstance(tweaks['series_index_auto_increment'], (int, float)): + return float(tweaks['series_index_auto_increment']) + return 1.0 def set(self, row, column, val): ''' @@ -1565,6 +1612,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if notify: self.notify('metadata', [id]) + def set_title_sort(self, id, title_sort_, notify=True, commit=True): + if not title_sort_: + return False + if isbytestring(title_sort_): + title_sort_ = title_sort_.decode(preferred_encoding, 'replace') + self.conn.execute('UPDATE books SET sort=? WHERE id=?', (title_sort_, id)) + self.data.set(id, self.FIELD_MAP['sort'], title_sort_, row_is_id=True) + self.dirtied([id], commit=False) + if commit: + self.conn.commit() + if notify: + self.notify('metadata', [id]) + return True + def _set_title(self, id, title): if not title: return False @@ -1746,18 +1807,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): FROM books, books_series_link as lt WHERE books.id = lt.book AND lt.series=? ORDER BY books.series_index''', (old_id,)) - # Get the next series index - index = self.get_next_series_num_for(new_name) # Now update the link table self.conn.execute('''UPDATE books_series_link SET series=? WHERE series=?''',(new_id, old_id,)) # Now set the indices for (book_id,) in books: + # Get the next series index + index = self.get_next_series_num_for(new_name) self.conn.execute('''UPDATE books SET series_index=? WHERE id=?''',(index, book_id,)) - index = index + 1 self.dirty_books_referencing('series', new_id, commit=False) self.conn.commit() diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index 5e581e3f86..597cee8cfd 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -429,3 +429,13 @@ class SchemaUpgrade(object): 'Remove commas from tags' self.conn.execute("UPDATE tags SET name=REPLACE(name, ',', ';')") + def upgrade_version_16(self): + self.conn.executescript(''' + DROP TRIGGER IF EXISTS books_update_trg; + CREATE TRIGGER books_update_trg + AFTER UPDATE ON books + BEGIN + UPDATE books SET sort=title_sort(NEW.title) + WHERE id=NEW.id AND OLD.title <> NEW.title; + END; + ''')