diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 1a408939df..aa43785292 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -11,7 +11,7 @@ import os, traceback, random, shutil, operator from io import BytesIO from collections import defaultdict, Set, MutableSet from functools import wraps, partial -from polyglot.builtins import iteritems, itervalues, unicode_type, zip, string_or_bytes +from polyglot.builtins import iteritems, itervalues, unicode_type, zip, string_or_bytes, cmp from time import time from calibre import isbytestring, as_unicode @@ -956,14 +956,14 @@ class Cache(object): class SortKey(object): - __slots__ = ('book_id', 'sort_key') + __slots__ = 'book_id', 'sort_key' def __init__(self, book_id): self.book_id = book_id # Calculate only the first sub-sort key since that will always be used self.sort_key = [key(book_id) if i == 0 else Lazy for i, key in enumerate(sort_key_funcs)] - def __cmp__(self, other): + def compare_to_other(self, other): for i, (order, self_key, other_key) in enumerate(zip(orders, self.sort_key, other.sort_key)): if self_key is Lazy: self_key = self.sort_key[i] = sort_key_funcs[i](self.book_id) @@ -974,6 +974,24 @@ class Cache(object): return ans * order return 0 + def __eq__(self, other): + return self.compare_to_other(other) == 0 + + def __ne__(self, other): + return self.compare_to_other(other) != 0 + + def __lt__(self, other): + return self.compare_to_other(other) < 0 + + def __le__(self, other): + return self.compare_to_other(other) <= 0 + + def __gt__(self, other): + return self.compare_to_other(other) > 0 + + def __ge__(self, other): + return self.compare_to_other(other) >= 0 + return sorted(ids_to_sort, key=SortKey) @read_api diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 349e104458..77889afe5f 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -8,13 +8,14 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' import re, threading +from functools import total_ordering from calibre import browser, random_user_agent from calibre.customize import Plugin from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.author_mapper import cap_author_token from calibre.utils.localization import canonicalize_lang, get_lang -from polyglot.builtins import iteritems +from polyglot.builtins import iteritems, cmp def create_log(ostream=None): @@ -41,6 +42,7 @@ def cleanup_title(s): return s.strip() +@total_ordering class InternalMetadataCompareKeyGen(object): ''' @@ -86,21 +88,38 @@ class InternalMetadataCompareKeyGen(object): source_plugin.get_cached_cover_url(mi.identifiers) is None) else 1 self.base = (same_identifier, has_cover, all_fields, language, exact_title) - self.comments_len = len(mi.comments.strip() if mi.comments else '') - self.extra = (getattr(mi, 'source_relevance', 0), ) + self.comments_len = len((mi.comments or '').strip()) + self.extra = getattr(mi, 'source_relevance', 0) - def __cmp__(self, other): - result = cmp(self.base, other.base) - if result == 0: - # Now prefer results with the longer comments, within 10% - cx, cy = self.comments_len, other.comments_len + def compare_to_other(self, other): + a = cmp(self.base, other.base) + if a != 0: + return a + cx, cy = self.comments_len, other.comments_len + if cx and cy: t = (cx + cy) / 20 delta = cy - cx if abs(delta) > t: - result = delta - else: - result = cmp(self.extra, other.extra) - return result + return -1 if delta < 0 else 1 + return cmp(self.extra, other.extra) + + def __eq__(self, other): + return self.compare_to_other(other) == 0 + + def __ne__(self, other): + return self.compare_to_other(other) != 0 + + def __lt__(self, other): + return self.compare_to_other(other) < 0 + + def __le__(self, other): + return self.compare_to_other(other) <= 0 + + def __gt__(self, other): + return self.compare_to_other(other) > 0 + + def __ge__(self, other): + return self.compare_to_other(other) >= 0 # }}} diff --git a/src/calibre/gui2/preferences/tweaks.py b/src/calibre/gui2/preferences/tweaks.py index de18f88e34..93f9a4866f 100644 --- a/src/calibre/gui2/preferences/tweaks.py +++ b/src/calibre/gui2/preferences/tweaks.py @@ -6,6 +6,7 @@ import textwrap from collections import OrderedDict from functools import partial +from operator import attrgetter from PyQt5.Qt import ( QAbstractListModel, QApplication, QDialog, QDialogButtonBox, QFont, QGridLayout, @@ -104,9 +105,9 @@ class Tweak(object): # {{{ ans = ans.encode('utf-8') return ans - def __cmp__(self, other): - return -1 * cmp(self.is_customized, - getattr(other, 'is_customized', False)) + @property + def sort_key(self): + return 0 if self.is_customized else 1 @property def is_customized(self): @@ -190,7 +191,7 @@ class Tweaks(QAbstractListModel, AdaptSQP): # {{{ pos = self.read_tweak(lines, pos, default_tweaks, custom_tweaks) pos += 1 - self.tweaks.sort() + self.tweaks.sort(key=attrgetter('sort_key')) default_keys = set(default_tweaks) custom_keys = set(custom_tweaks) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 21413d4e31..359d6418cf 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -1113,13 +1113,31 @@ class SortKey(object): def __init__(self, orders, values): self.orders, self.values = orders, values - def __cmp__(self, other): + def compare_to_other(self, other): for i, ascending in enumerate(self.orders): ans = cmp(self.values[i], other.values[i]) if ans != 0: return ans * ascending return 0 + def __eq__(self, other): + return self.compare_to_other(other) == 0 + + def __ne__(self, other): + return self.compare_to_other(other) != 0 + + def __lt__(self, other): + return self.compare_to_other(other) < 0 + + def __le__(self, other): + return self.compare_to_other(other) <= 0 + + def __gt__(self, other): + return self.compare_to_other(other) > 0 + + def __ge__(self, other): + return self.compare_to_other(other) >= 0 + class SortKeyGenerator(object): diff --git a/src/calibre/utils/ipc/job.py b/src/calibre/utils/ipc/job.py index 70cd4612c6..881575705e 100644 --- a/src/calibre/utils/ipc/job.py +++ b/src/calibre/utils/ipc/job.py @@ -6,13 +6,17 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -_count = 0 import time, io +from itertools import count from calibre import prints from calibre.constants import DEBUG from polyglot.queue import Queue, Empty +from polyglot.builtins import cmp + + +job_counter = count() class BaseJob(object): @@ -22,10 +26,7 @@ class BaseJob(object): FINISHED = 2 def __init__(self, description, done=lambda x: x): - global _count - _count += 1 - - self.id = _count + self.id = next(job_counter) self.description = description self.done = done self.done2 = None @@ -125,22 +126,36 @@ class BaseJob(object): def is_running(self): return self.is_started and not self.is_finished - def __cmp__(self, other): - if self.is_finished == other.is_finished: - if self.start_time is None: - if other.start_time is None: # Both waiting - return cmp(other.id, self.id) - else: - return 1 - else: - if other.start_time is None: - return -1 - else: # Both running - return cmp(other.start_time, self.start_time) + def __eq__(self, other): + return self is other - else: + def __ne__(self, other): + return self is not other + + def __lt__(self, other): + return self.compare_to_other(other) < 0 + + def __le__(self, other): + return self.compare_to_other(other) <= 0 + + def __gt__(self, other): + return self.compare_to_other(other) > 0 + + def __ge__(self, other): + return self.compare_to_other(other) >= 0 + + def compare_to_other(self, other): + if self.is_finished != other.is_finished: return 1 if self.is_finished else -1 - return 0 + + if self.start_time is None: + if other.start_time is None: # Both waiting + return cmp(other.id, self.id) + return 1 + if other.start_time is None: + return -1 + # Both running + return cmp((other.start_time, id(other)), (self.start_time, id(self))) @property def log_file(self): diff --git a/src/calibre/web/feeds/recipes/model.py b/src/calibre/web/feeds/recipes/model.py index 910076fd24..458aa347ed 100644 --- a/src/calibre/web/feeds/recipes/model.py +++ b/src/calibre/web/feeds/recipes/model.py @@ -7,6 +7,7 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import copy, zipfile +from functools import total_ordering from PyQt5.Qt import QAbstractItemModel, Qt, QColor, QFont, QIcon, \ QModelIndex, pyqtSignal, QPixmap @@ -18,6 +19,8 @@ from calibre.web.feeds.recipes.collection import \ SchedulerConfig, download_builtin_recipe, update_custom_recipe, \ update_custom_recipes, add_custom_recipe, add_custom_recipes, \ remove_custom_recipe, get_custom_recipe, get_builtin_recipe +from calibre import force_unicode +from calibre.utils.icu import primary_sort_key from calibre.utils.search_query_parser import ParseException from polyglot.builtins import iteritems, unicode_type @@ -59,12 +62,19 @@ class NewsTreeItem(object): child.parent = None +@total_ordering class NewsCategory(NewsTreeItem): def __init__(self, category, builtin, custom, scheduler_config, parent): NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent) self.category = category self.cdata = get_language(self.category) + if self.category == _('Scheduled'): + self.sortq = 0, '' + elif self.category == _('Custom'): + self.sortq = 1, '' + else: + self.sortq = 2, self.cdata self.bold_font = QFont() self.bold_font.setBold(True) self.bold_font = (self.bold_font) @@ -81,25 +91,23 @@ class NewsCategory(NewsTreeItem): def flags(self): return Qt.ItemIsEnabled - def __cmp__(self, other): - def decorate(x): - if x == _('Scheduled'): - x = '0' + x - elif x == _('Custom'): - x = '1' + x - else: - x = '2' + x - return x + def __eq__(self, other): + return self.cdata == other.cdata - return cmp(decorate(self.cdata), decorate(getattr(other, 'cdata', ''))) + def __lt__(self, other): + return self.sortq < getattr(other, 'sortq', (3, '')) +@total_ordering class NewsItem(NewsTreeItem): def __init__(self, urn, title, default_icon, custom_icon, favicons, zf, builtin, custom, scheduler_config, parent): NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent) self.urn, self.title = urn, title + if isinstance(self.title, bytes): + self.title = force_unicode(self.title) + self.sortq = primary_sort_key(self.title) self.icon = self.default_icon = None self.default_icon = default_icon self.favicons, self.zf = favicons, zf @@ -126,8 +134,11 @@ class NewsItem(NewsTreeItem): return self.icon return None - def __cmp__(self, other): - return cmp(self.title.lower(), getattr(other, 'title', '').lower()) + def __eq__(self, other): + return self.urn == other.urn + + def __lt__(self, other): + return self.sortq < other.sortq class AdaptSQP(SearchQueryParser): diff --git a/src/polyglot/builtins.py b/src/polyglot/builtins.py index d66cb9c576..67b194a97b 100644 --- a/src/polyglot/builtins.py +++ b/src/polyglot/builtins.py @@ -56,6 +56,9 @@ if is_py3: code = compile(code, f.name, 'exec') exec(code, ctx) + def cmp(a, b): + return (a > b) - (a < b) + else: exec("""def reraise(tp, value, tb=None): try: @@ -74,6 +77,7 @@ else: long_type = long exec_path = execfile raw_input = builtins.raw_input + cmp = builtins.cmp def iteritems(d): return d.iteritems()