From 419f3b63947815ba32b3ab2d770ec046694bf8e1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 19 Jan 2013 22:37:27 +0530 Subject: [PATCH] Add language dependent sorting for series like columns --- src/calibre/db/cache.py | 22 +++++++++---- src/calibre/db/fields.py | 33 +++++++++++++++---- src/calibre/db/tests/metadata.db | Bin 230400 -> 230400 bytes src/calibre/db/tests/reading.py | 53 ++++++++++++++++--------------- 4 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 10fe0bb014..a631f9ea46 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -269,11 +269,11 @@ class Cache(object): return () @read_api - def all_book_ids(self): + def all_book_ids(self, type=frozenset): ''' Frozen set of all known book ids. ''' - return frozenset(self.fields['uuid']) + return type(self.fields['uuid']) @read_api def all_field_ids(self, name): @@ -316,6 +316,10 @@ class Cache(object): self.format_metadata_cache[book_id][fmt] = ans return ans + @read_api + def pref(self, name): + return self.backend.prefs[name] + @api def get_metadata(self, book_id, get_cover=False, get_user_categories=True, cover_as_data=False): @@ -378,17 +382,21 @@ class Cache(object): all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None else ids_to_sort) get_metadata = partial(self._get_metadata, get_user_categories=False) + def get_lang(book_id): + ans = self._field_for('languages', book_id) + return ans[0] if ans else None fm = {'title':'sort', 'authors':'author_sort'} def sort_key(field): 'Handle series type fields' - ans = self.fields[fm.get(field, field)].sort_keys_for_books(get_metadata, - all_book_ids) idx = field + '_index' - if idx in self.fields: - idx_ans = self.fields[idx].sort_keys_for_books(get_metadata, - all_book_ids) + is_series = idx in self.fields + ans = self.fields[fm.get(field, field)].sort_keys_for_books( + get_metadata, get_lang, all_book_ids,) + if is_series: + idx_ans = self.fields[idx].sort_keys_for_books( + get_metadata, get_lang, all_book_ids) ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()} return ans diff --git a/src/calibre/db/fields.py b/src/calibre/db/fields.py index e154900031..3808052549 100644 --- a/src/calibre/db/fields.py +++ b/src/calibre/db/fields.py @@ -11,6 +11,8 @@ __docformat__ = 'restructuredtext en' from threading import Lock from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY +from calibre.ebooks.metadata import title_sort +from calibre.utils.config_base import tweaks from calibre.utils.icu import sort_key from calibre.utils.date import UNDEFINED_DATE from calibre.utils.localization import calibre_langcode_to_name @@ -72,7 +74,7 @@ class Field(object): ''' return iter(()) - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): ''' Return a mapping of book_id -> sort_key. The sort key is suitable for use in sorting the list of all books by this field, via the python cmp @@ -96,7 +98,7 @@ class OneToOneField(Field): def __iter__(self): return self.table.book_col_map.iterkeys() - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): return {id_ : self._sort_key(self.table.book_col_map.get(id_, self._default_sort_key)) for id_ in all_book_ids} @@ -133,7 +135,7 @@ class CompositeField(OneToOneField): ans = mi.get('#'+self.metadata['label']) return ans - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): return {id_ : sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in all_book_ids} @@ -170,7 +172,7 @@ class OnDeviceField(OneToOneField): def __iter__(self): return iter(()) - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): return {id_ : self.for_book(id_) for id_ in all_book_ids} @@ -196,7 +198,7 @@ class ManyToOneField(Field): def __iter__(self): return self.table.id_map.iterkeys() - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): ans = {id_ : self.table.book_col_map.get(id_, None) for id_ in all_book_ids} sk_map = {cid : (self._default_sort_key if cid is None else @@ -227,7 +229,7 @@ class ManyToManyField(Field): def __iter__(self): return self.table.id_map.iterkeys() - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): ans = {id_ : self.table.book_col_map.get(id_, ()) for id_ in all_book_ids} all_cids = set() @@ -248,7 +250,7 @@ class IdentifiersField(ManyToManyField): ids = default_value return ids - def sort_keys_for_books(self, get_metadata, all_book_ids): + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): 'Sort by identifier keys' ans = {id_ : self.table.book_col_map.get(id_, ()) for id_ in all_book_ids} @@ -274,6 +276,21 @@ class FormatsField(ManyToManyField): def format_fname(self, book_id, fmt): return self.table.fname_map[book_id][fmt.upper()] +class SeriesField(ManyToOneField): + + def sort_key_for_series(self, book_id, get_lang, series_sort_order): + sid = self.table.book_col_map.get(book_id, None) + if sid is None: + return self._default_sort_key + return self._sort_key(title_sort(self.table.id_map[sid], + order=series_sort_order, + lang=get_lang(book_id))) + + def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): + sso = tweaks['title_series_sorting'] + return {book_id:self.sort_key_for_series(book_id, get_lang, sso) for book_id + in all_book_ids} + def create_field(name, table): cls = { ONE_ONE : OneToOneField, @@ -290,5 +307,7 @@ def create_field(name, table): cls = IdentifiersField elif table.metadata['datatype'] == 'composite': cls = CompositeField + elif table.metadata['datatype'] == 'series': + cls = SeriesField return cls(name, table) diff --git a/src/calibre/db/tests/metadata.db b/src/calibre/db/tests/metadata.db index 63a096e2f44d9ecce9f32da2f408d56ebe6949ce..da54c61ad5c83bde8917933e2d73f2e6ae370273 100644 GIT binary patch delta 991 zcmaKrTWC{B9L8tPcbdjnn?zHr_Oi#;P+N_?rlK253%XjWHnv^pO=8jW3085} zq!qmElM>-CvJVvm@nzQ%aUN8#DEK0*^}6e=X?+k8S`>?*4|Ps-7eVyEcjjZh;Wu-> z;lz(-$B$+o&zsZ17#sgAeqzarj1K0SC>ail?D^HMib_}IO0BYXRgHU9m3w(@ZPiM* zJ5gmF{#KTJZ?&GqTf9x$D_$Wdi*t!Kam6ABrZOg@_vy%-#T6q`k~5dNnmb2&Gq*7Q zPMN2aDr=QrltapCZb{@`dt(_Ga0X4Rba1eohCCRP7&ex;Jd&T79I56$dKyD<-h98x z%I#sjKjMq-^aXTZWPi9v4|Z+I36R%=9P{s8JsE7>$(T5J5XBsoH5Cclh=W87i{~1r zGsDC15-m6n9iF7^Mj2c=DCb`x{c};uw}tfHps!Dl`h$L{MOep4f(tTw(sn#1tuGgD zP6Hk|5+gTDcp&-mW`V;r!7zbaNxS(v5`T})E6az@9fyQhejLVq~<0xlGcSE#RV@~!SB-Q2CSh!Ho+}^Yr|pid*XE%R!;Q?kilzc z*b7aR?Z93x{^~><=l4at8*$KwI+W1TcKmGY{y@aH8k4p`Sm|057LuzQtw!>Y&g_Dg zS@h}iXtEB~O0_$UHAT|h(B^g&n$p%s;dTs&b0Ksnv@`$<9k0g%D%pw-gY^$B3cx{s zHcS4Chzz`qFpI2SY{Y-8zB+VX3PM$BGJs>^r(KB1i(u?AjkiFf!cLqJ;V7EYlTT>W zj|y>dKQ<^-*e|JtQFv)6giJBjE=w@3>FF}fqG?5RCWNy8>njpJ3}T$qRWBSOcOQDW zvB@)vt;Q44U56n~<;~bmGn+6=MB}jWOZ9+i{0`|SCEqE^`W1Nv52U>>bx-Q9)E%i& Usg%@h+TDVDN<}55FOCF00>i)|-v9sr delta 1190 zcmbVLU2GIp6ux)9*>%CPZK0J)f0(5PTWE3r+GPu-)&6RU{f|;w+JzmqtJ@!Ux3#u3 z6cd4HjF2H5OiYBtgb+h?vyuBC5426h2NQu(ph0QHgoN-S#J+&B-f0?$FUEK?U*_EN zopa{gIo};WVjn+ZKbpO&kumnG#L_xe;IlNc;yddTL1AC(Dt7vdoj%Q3TIMb-EAehA z_4!;Lzu`_zJmpHtJl-<5XUkTX)8p|bAEl;#iAVS&neM%h%oMK3x>k;rW_gvdCzLHk zS)!0!{En+x7D;nFHlyb8SWy*Cspj7^W@KG_m=oJjrulqD+Dc8X4e@ZV9*YNh2TZTu zxILBU2*-N#sNDLW1VsXUp}{~%H&dNO8YM!=pza_txu0f(DDtf1DsyG->F=wm2FNRjOo#?z0wuxkB0Tw=$Nr)(!<-0+R1#|$Ie|AR(qn**nIJ{ zQ91R2;`bUqOnFHSqAgpV;c|z0;Ahaxa1YHm3muaI(_Yr!`0 zO&wamXXxcIRMF=(C=p{ja0vW5P3*yo^wpbaqc3_97Wbo49<;s!8lBsT6mmxJq4>HP zt(@PWL?sI8+%B}xVjXNW-HO#BuNC{`3>dpf>E+0$GhNt8pH@k0ya8V7eg_V*|1H>X zJ_uE{4ofY%y_BefCj3np;^Ku)bW5!~x`4w&m`|^JdI1G# zC_@allp#tRN4DdJ^jw#ik+>!?Epb)iio~46WqS8@`H#8sr>&oF_IHoXZxsq tx4b0B$NqUqwk}sPJDsD`JqXca4OWQXN07#;p&C{?wHH=#^dJV5zX2&wWI6x< diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py index b722d30793..d77d3ac6eb 100644 --- a/src/calibre/db/tests/reading.py +++ b/src/calibre/db/tests/reading.py @@ -63,7 +63,7 @@ class ReadingTest(BaseTest): 'sort': 'One', 'authors': ('Author One',), 'author_sort': 'One, Author', - 'series' : 'Series One', + 'series' : 'A Series One', 'series_index': 1.0, 'tags':('Tag Two', 'Tag One'), 'formats': (), @@ -92,7 +92,7 @@ class ReadingTest(BaseTest): 'sort': 'Title Two', 'authors': ('Author Two', 'Author One'), 'author_sort': 'Two, Author & One, Author', - 'series' : 'Series One', + 'series' : 'A Series One', 'series_index': 2.0, 'rating': 6.0, 'tags': ('Tag One',), @@ -130,30 +130,31 @@ class ReadingTest(BaseTest): 'Test sorting' cache = self.init_cache(self.library_path) for field, order in { - 'title' : [2, 1, 3], - 'authors': [2, 1, 3], - 'series' : [3, 2, 1], - 'tags' : [3, 1, 2], - 'rating' : [3, 2, 1], - # 'identifiers': [3, 2, 1], There is no stable sort since 1 and - # 2 have the same identifier keys - # TODO: Add an empty book to the db and ensure that empty - # fields sort the same as they do in db2 - 'timestamp': [2, 1, 3], - 'pubdate' : [1, 2, 3], - 'publisher': [3, 2, 1], - 'last_modified': [2, 1, 3], - 'languages': [3, 2, 1], - 'comments': [3, 2, 1], - '#enum' : [3, 2, 1], - '#authors' : [3, 2, 1], - '#date': [3, 1, 2], - '#rating':[3, 2, 1], - '#series':[3, 2, 1], - '#tags':[3, 2, 1], - '#yesno':[3, 1, 2], - '#comments':[3, 2, 1], - }.iteritems(): + 'title' : [2, 1, 3], + 'authors': [2, 1, 3], + 'series' : [3, 1, 2], + 'tags' : [3, 1, 2], + 'rating' : [3, 2, 1], + # 'identifiers': [3, 2, 1], There is no stable sort since 1 and + # 2 have the same identifier keys + # 'last_modified': [3, 2, 1], There is no stable sort as two + # records have the exact same value + 'timestamp': [2, 1, 3], + 'pubdate' : [1, 2, 3], + 'publisher': [3, 2, 1], + 'languages': [3, 2, 1], + 'comments': [3, 2, 1], + '#enum' : [3, 2, 1], + '#authors' : [3, 2, 1], + '#date': [3, 1, 2], + '#rating':[3, 2, 1], + '#series':[3, 2, 1], + '#tags':[3, 2, 1], + '#yesno':[3, 1, 2], + '#comments':[3, 2, 1], + # TODO: Add an empty book to the db and ensure that empty + # fields sort the same as they do in db2 + }.iteritems(): x = list(reversed(order)) self.assertEqual(order, cache.multisort([(field, True)], ids_to_sort=x),