home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 January / maximum-cd-2011-01.iso / DiscContents / calibre-0.7.26.msi / file_1404 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-10-31  |  26.7 KB  |  1,003 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL v3'
  6. __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
  7. __docformat__ = 'restructuredtext en'
  8. import re
  9. import itertools
  10. import time
  11. import traceback
  12. from itertools import repeat
  13. from datetime import timedelta
  14. from threading import Thread, RLock
  15. from Queue import Queue, Empty
  16. from PyQt4.Qt import QImage, Qt
  17. from calibre.utils.config import tweaks
  18. from calibre.utils.date import parse_date, now, UNDEFINED_DATE
  19. from calibre.utils.search_query_parser import SearchQueryParser
  20. from calibre.utils.pyparsing import ParseException
  21. from calibre.ebooks.metadata import title_sort
  22. from calibre.ebooks.metadata.opf2 import metadata_to_opf
  23. from calibre import fit_image, prints
  24.  
  25. class MetadataBackup(Thread):
  26.     
  27.     def __init__(self, db):
  28.         Thread.__init__(self)
  29.         self.daemon = True
  30.         self.db = db
  31.         self.keep_running = True
  32.         FunctionDispatcher = FunctionDispatcher
  33.         import calibre.gui2
  34.         self.do_write = FunctionDispatcher(self.write)
  35.         self.get_metadata_for_dump = FunctionDispatcher(db.get_metadata_for_dump)
  36.         self.clear_dirtied = FunctionDispatcher(db.clear_dirtied)
  37.         self.set_dirtied = FunctionDispatcher(db.dirtied)
  38.         self.in_limbo = None
  39.  
  40.     
  41.     def stop(self):
  42.         self.keep_running = False
  43.  
  44.     
  45.     def run(self):
  46.         while self.keep_running:
  47.             self.in_limbo = None
  48.             
  49.             try:
  50.                 time.sleep(0.5)
  51.                 id_ = self.db.dirtied_queue.get(True, 1.45)
  52.             except Empty:
  53.                 continue
  54.             except:
  55.                 break
  56.  
  57.             
  58.             try:
  59.                 (path, mi) = self.get_metadata_for_dump(id_)
  60.             except:
  61.                 prints('Failed to get backup metadata for id:', id_, 'once')
  62.                 traceback.print_exc()
  63.                 time.sleep(2)
  64.                 
  65.                 try:
  66.                     (path, mi) = self.get_metadata_for_dump(id_)
  67.                 prints('Failed to get backup metadata for id:', id_, 'again, giving up')
  68.                 traceback.print_exc()
  69.                 continue
  70.  
  71.  
  72.             if mi is None:
  73.                 continue
  74.             
  75.             self.in_limbo = id_
  76.             time.sleep(0.1)
  77.             
  78.             try:
  79.                 raw = metadata_to_opf(mi)
  80.             except:
  81.                 self.set_dirtied([
  82.                     id_])
  83.                 prints('Failed to convert to opf for id:', id_)
  84.                 traceback.print_exc()
  85.                 continue
  86.  
  87.             time.sleep(0.1)
  88.             
  89.             try:
  90.                 self.do_write(path, raw)
  91.             continue
  92.             prints('Failed to write backup metadata for id:', id_, 'once')
  93.             time.sleep(2)
  94.             try:
  95.                 self.do_write(path, raw)
  96.             self.set_dirtied([
  97.                 id_])
  98.             prints('Failed to write backup metadata for id:', id_, 'again, giving up')
  99.             continue
  100.  
  101.             continue
  102.         self.in_limbo = None
  103.  
  104.     
  105.     def flush(self):
  106.         if self.in_limbo is not None:
  107.             
  108.             try:
  109.                 self.db.dirtied([
  110.                     self.in_limbo])
  111.             traceback.print_exc()
  112.  
  113.         
  114.  
  115.     
  116.     def write(self, path, raw):
  117.         
  118.         try:
  119.             f = _[1]
  120.             f.write(raw)
  121.         finally:
  122.             pass
  123.  
  124.  
  125.  
  126.  
  127. class CoverCache(Thread):
  128.     
  129.     def __init__(self, db, cover_func):
  130.         Thread.__init__(self)
  131.         self.daemon = True
  132.         self.db = db
  133.         self.cover_func = cover_func
  134.         self.load_queue = Queue()
  135.         self.keep_running = True
  136.         self.cache = { }
  137.         self.lock = RLock()
  138.         self.null_image = QImage()
  139.  
  140.     
  141.     def stop(self):
  142.         self.keep_running = False
  143.  
  144.     
  145.     def _image_for_id(self, id_):
  146.         img = self.cover_func(id_, index_is_id = True, as_image = True)
  147.         if img is None:
  148.             img = QImage()
  149.         
  150.         if not img.isNull():
  151.             (scaled, nwidth, nheight) = fit_image(img.width(), img.height(), 600, 800)
  152.             if scaled:
  153.                 img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio, Qt.SmoothTransformation)
  154.             
  155.         
  156.         return img
  157.  
  158.     
  159.     def run(self):
  160.         while self.keep_running:
  161.             
  162.             try:
  163.                 ids = set()
  164.                 id_ = self.load_queue.get(True, 2)
  165.                 ids.add(id_)
  166.                 
  167.                 try:
  168.                     while True:
  169.                         id_ = self.load_queue.get(True, 0.5)
  170.                         ids.add(id_)
  171.                 except Empty:
  172.                     pass
  173.                 except:
  174.                     break
  175.  
  176.             except Empty:
  177.                 continue
  178.             except:
  179.                 break
  180.  
  181.             if not self.keep_running:
  182.                 break
  183.             
  184.             for id_ in ids:
  185.                 time.sleep(0.05)
  186.                 
  187.                 try:
  188.                     img = self._image_for_id(id_)
  189.                 except:
  190.                     
  191.                     try:
  192.                         traceback.print_exc()
  193.                     continue
  194.                     break
  195.                     continue
  196.  
  197.                     continue
  198.  
  199.                 
  200.                 try:
  201.                     self.lock.__enter__()
  202.                     
  203.                     try:
  204.                         self.cache[id_] = img
  205.                     finally:
  206.                         pass
  207.  
  208.                 continue
  209.                 break
  210.                 continue
  211.  
  212.             
  213.  
  214.     
  215.     def set_cache(self, ids):
  216.         self.lock.__enter__()
  217.         
  218.         try:
  219.             already_loaded = set([])
  220.             for id in self.cache.keys():
  221.                 if id in ids:
  222.                     already_loaded.add(id)
  223.                     continue
  224.                 self.lock.__exit__
  225.                 self.cache.pop(id)
  226.         finally:
  227.             pass
  228.  
  229.         for id_ in set(ids) - already_loaded:
  230.             self.load_queue.put(id_)
  231.         
  232.  
  233.     
  234.     def cover(self, id_):
  235.         self.lock.__enter__()
  236.         
  237.         try:
  238.             return self.cache.get(id_, self.null_image)
  239.         finally:
  240.             pass
  241.  
  242.  
  243.     
  244.     def clear_cache(self):
  245.         self.lock.__enter__()
  246.         
  247.         try:
  248.             self.cache = { }
  249.         finally:
  250.             pass
  251.  
  252.  
  253.     
  254.     def refresh(self, ids):
  255.         self.lock.__enter__()
  256.         
  257.         try:
  258.             for id_ in ids:
  259.                 self.cache.pop(id_, None)
  260.                 self.load_queue.put(id_)
  261.         finally:
  262.             pass
  263.  
  264.  
  265.  
  266. CONTAINS_MATCH = 0
  267. EQUALS_MATCH = 1
  268. REGEXP_MATCH = 2
  269.  
  270. def _match(query, value, matchkind):
  271.     for t in value:
  272.         t = t.lower()
  273.         
  274.         try:
  275.             if not matchkind == EQUALS_MATCH or query == t:
  276.                 if (matchkind == REGEXP_MATCH or re.search(query, t, re.I) or matchkind == CONTAINS_MATCH) and query in t:
  277.                     return True
  278.         continue
  279.         except re.error:
  280.             continue
  281.         
  282.  
  283.     
  284.     return False
  285.  
  286.  
  287. class ResultCache(SearchQueryParser):
  288.     
  289.     def __init__(self, FIELD_MAP, field_metadata):
  290.         self.FIELD_MAP = FIELD_MAP
  291.         self._map = self._data = self._map_filtered = []
  292.         self.first_sort = True
  293.         self.search_restriction = ''
  294.         self.field_metadata = field_metadata
  295.         self.all_search_locations = field_metadata.get_search_terms()
  296.         SearchQueryParser.__init__(self, self.all_search_locations)
  297.         self.build_date_relop_dict()
  298.         self.build_numeric_relop_dict()
  299.         self.composites = []
  300.         for key in field_metadata:
  301.             if field_metadata[key]['datatype'] == 'composite':
  302.                 self.composites.append((key, field_metadata[key]['rec_index']))
  303.                 continue
  304.         
  305.  
  306.     
  307.     def __getitem__(self, row):
  308.         return self._data[self._map_filtered[row]]
  309.  
  310.     
  311.     def __len__(self):
  312.         return len(self._map_filtered)
  313.  
  314.     
  315.     def __iter__(self):
  316.         for id in self._map_filtered:
  317.             yield self._data[id]
  318.         
  319.  
  320.     
  321.     def iterall(self):
  322.         for x in self._data:
  323.             if x is not None:
  324.                 yield x
  325.                 continue
  326.         
  327.  
  328.     
  329.     def iterallids(self):
  330.         idx = self.FIELD_MAP['id']
  331.         for x in self.iterall():
  332.             yield x[idx]
  333.         
  334.  
  335.     
  336.     def universal_set(self):
  337.         return [](_[1])
  338.  
  339.     
  340.     def build_date_relop_dict(self):
  341.         
  342.         def relop_eq(db, query, field_count):
  343.             return False
  344.  
  345.         
  346.         def relop_gt(db, query, field_count):
  347.             if db.year > query.year:
  348.                 return True
  349.             if field_count > 1 and db.year == query.year:
  350.                 if db.month > query.month:
  351.                     return True
  352.                 if field_count == 3 and db.month == query.month:
  353.                     pass
  354.                 return db.day > query.day
  355.             return False
  356.  
  357.         
  358.         def relop_lt(db, query, field_count):
  359.             if db.year < query.year:
  360.                 return True
  361.             if field_count > 1 and db.year == query.year:
  362.                 if db.month < query.month:
  363.                     return True
  364.                 if field_count == 3 and db.month == query.month:
  365.                     pass
  366.                 return db.day < query.day
  367.             return False
  368.  
  369.         
  370.         def relop_ne(db, query, field_count):
  371.             return not relop_eq(db, query, field_count)
  372.  
  373.         
  374.         def relop_ge(db, query, field_count):
  375.             return not relop_lt(db, query, field_count)
  376.  
  377.         
  378.         def relop_le(db, query, field_count):
  379.             return not relop_gt(db, query, field_count)
  380.  
  381.         self.date_search_relops = {
  382.             '=': [
  383.                 1,
  384.                 relop_eq],
  385.             '>': [
  386.                 1,
  387.                 relop_gt],
  388.             '<': [
  389.                 1,
  390.                 relop_lt],
  391.             '!=': [
  392.                 2,
  393.                 relop_ne],
  394.             '>=': [
  395.                 2,
  396.                 relop_ge],
  397.             '<=': [
  398.                 2,
  399.                 relop_le] }
  400.  
  401.     
  402.     def get_dates_matches(self, location, query):
  403.         matches = set([])
  404.         if len(query) < 2:
  405.             return matches
  406.         if location == 'date':
  407.             location = 'timestamp'
  408.         
  409.         loc = self.field_metadata[location]['rec_index']
  410.         if query == 'false':
  411.             for item in self._data:
  412.                 if item is None:
  413.                     continue
  414.                 
  415.                 if item[loc] is None or item[loc] <= UNDEFINED_DATE:
  416.                     matches.add(item[0])
  417.                     continue
  418.             
  419.             return matches
  420.         if query == 'true':
  421.             for item in self._data:
  422.                 if item is None:
  423.                     continue
  424.                 
  425.                 if item[loc] is not None and item[loc] > UNDEFINED_DATE:
  426.                     matches.add(item[0])
  427.                     continue
  428.             
  429.             return matches
  430.         relop = None
  431.         for k in self.date_search_relops.keys():
  432.             if query.startswith(k):
  433.                 (p, relop) = self.date_search_relops[k]
  434.                 query = query[p:]
  435.                 continue
  436.             query == 'true'
  437.         
  438.         if relop is None:
  439.             (p, relop) = self.date_search_relops['=']
  440.         
  441.         if query == _('today'):
  442.             qd = now()
  443.             field_count = 3
  444.         elif query == _('yesterday'):
  445.             qd = now() - timedelta(1)
  446.             field_count = 3
  447.         elif query == _('thismonth'):
  448.             qd = now()
  449.             field_count = 2
  450.         elif query.endswith(_('daysago')):
  451.             num = query[0:-len(_('daysago'))]
  452.             
  453.             try:
  454.                 qd = now() - timedelta(int(num))
  455.             except:
  456.                 raise ParseException(query, len(query), 'Number conversion error', self)
  457.  
  458.             field_count = 3
  459.         else:
  460.             
  461.             try:
  462.                 qd = parse_date(query, as_utc = False)
  463.             except:
  464.                 raise ParseException(query, len(query), 'Date conversion error', self)
  465.  
  466.             if '-' in query:
  467.                 field_count = query.count('-') + 1
  468.             else:
  469.                 field_count = query.count('/') + 1
  470.         for item in self._data:
  471.             if item is None or item[loc] is None:
  472.                 continue
  473.             
  474.             if relop(item[loc], qd, field_count):
  475.                 matches.add(item[0])
  476.                 continue
  477.         
  478.         return matches
  479.  
  480.     
  481.     def build_numeric_relop_dict(self):
  482.         self.numeric_search_relops = {
  483.             '=': [
  484.                 1,
  485.                 (lambda r, q: r == q)],
  486.             '>': [
  487.                 1,
  488.                 (lambda r, q: r > q)],
  489.             '<': [
  490.                 1,
  491.                 (lambda r, q: r < q)],
  492.             '!=': [
  493.                 2,
  494.                 (lambda r, q: r != q)],
  495.             '>=': [
  496.                 2,
  497.                 (lambda r, q: r >= q)],
  498.             '<=': [
  499.                 2,
  500.                 (lambda r, q: r <= q)] }
  501.  
  502.     
  503.     def get_numeric_matches(self, location, query):
  504.         matches = set([])
  505.         if len(query) == 0:
  506.             return matches
  507.         if query == 'false':
  508.             query = '0'
  509.         elif query == 'true':
  510.             query = '!=0'
  511.         
  512.         relop = None
  513.         for k in self.numeric_search_relops.keys():
  514.             if query.startswith(k):
  515.                 (p, relop) = self.numeric_search_relops[k]
  516.                 query = query[p:]
  517.                 continue
  518.         
  519.         if relop is None:
  520.             (p, relop) = self.numeric_search_relops['=']
  521.         
  522.         loc = self.field_metadata[location]['rec_index']
  523.         dt = self.field_metadata[location]['datatype']
  524.         if dt == 'int':
  525.             
  526.             cast = lambda x: int(x)
  527.             
  528.             adjust = lambda x: x
  529.         elif dt == 'rating':
  530.             
  531.             cast = lambda x: int(x)
  532.             
  533.             adjust = lambda x: x / 2
  534.         elif dt == 'float':
  535.             
  536.             cast = lambda x: float(x)
  537.             
  538.             adjust = lambda x: x
  539.         
  540.         if len(query) > 1:
  541.             mult = query[-1:].lower()
  542.             mult = {
  543.                 'k': 1024,
  544.                 'm': 1.04858e+06,
  545.                 'g': 1.07374e+09 }.get(mult, 1)
  546.             if mult != 1:
  547.                 query = query[:-1]
  548.             
  549.         else:
  550.             mult = 1
  551.         
  552.         try:
  553.             q = cast(query) * mult
  554.         except:
  555.             return matches
  556.  
  557.         for item in self._data:
  558.             if item is None:
  559.                 continue
  560.             
  561.             if not item[loc]:
  562.                 i = 0
  563.             else:
  564.                 i = adjust(item[loc])
  565.             if relop(i, q):
  566.                 matches.add(item[0])
  567.                 continue
  568.         
  569.         return matches
  570.  
  571.     
  572.     def get_matches(self, location, query, allow_recursion = True):
  573.         matches = set([])
  574.         if query and query.strip():
  575.             location = self.field_metadata.search_term_to_field_key(location.lower().strip())
  576.             if isinstance(location, list):
  577.                 if allow_recursion:
  578.                     for loc in location:
  579.                         matches |= self.get_matches(loc, query, allow_recursion = False)
  580.                     
  581.                     return matches
  582.                 raise ParseException(query, len(query), 'Recursive query group detected', self)
  583.             isinstance(location, list)
  584.             if location in self.field_metadata and self.field_metadata[location]['datatype'] == 'datetime':
  585.                 return self.get_dates_matches(location, query.lower())
  586.             if location in self.field_metadata and self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'):
  587.                 return self.get_numeric_matches(location, query.lower())
  588.             matchkind = CONTAINS_MATCH
  589.             if matchkind != REGEXP_MATCH:
  590.                 query = query.lower()
  591.             
  592.             if not isinstance(query, unicode):
  593.                 query = query.decode('utf-8')
  594.             
  595.             db_col = { }
  596.             exclude_fields = []
  597.             col_datatype = []
  598.             is_multiple_cols = { }
  599.             for x in range(len(self.FIELD_MAP)):
  600.                 col_datatype.append('')
  601.             
  602.             for x in self.field_metadata:
  603.                 if len(self.field_metadata[x]['search_terms']):
  604.                     db_col[x] = self.field_metadata[x]['rec_index']
  605.                     if self.field_metadata[x]['datatype'] not in ('composite', 'text', 'comments', 'series'):
  606.                         exclude_fields.append(db_col[x])
  607.                     
  608.                     col_datatype[db_col[x]] = self.field_metadata[x]['datatype']
  609.                     is_multiple_cols[db_col[x]] = self.field_metadata[x]['is_multiple']
  610.                     continue
  611.             
  612.             
  613.             try:
  614.                 rating_query = int(query) * 2
  615.             except:
  616.                 rating_query = None
  617.  
  618.             location = None if location != 'all' else list(db_col.keys())
  619.             for i, loc in enumerate(location):
  620.                 location[i] = db_col[loc]
  621.             
  622.             bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] == 'yes'
  623.             for loc in location:
  624.                 if loc == db_col['authors']:
  625.                     q = query.replace(',', '|')
  626.                 else:
  627.                     q = query
  628.                 for item in self._data:
  629.                     if item is None:
  630.                         continue
  631.                     
  632.                     if col_datatype[loc] == 'bool':
  633.                         v = item[loc]
  634.                         if not bools_are_tristate:
  635.                             if v is None or not v:
  636.                                 if q in [
  637.                                     _('no'),
  638.                                     _('unchecked'),
  639.                                     'false']:
  640.                                     matches.add(item[0])
  641.                                 
  642.                             elif q in [
  643.                                 _('yes'),
  644.                                 _('checked'),
  645.                                 'true']:
  646.                                 matches.add(item[0])
  647.                             
  648.                         not v
  649.                         if v is None:
  650.                             if q in [
  651.                                 _('empty'),
  652.                                 _('blank'),
  653.                                 'false']:
  654.                                 matches.add(item[0])
  655.                             
  656.                         q in [
  657.                             _('empty'),
  658.                             _('blank'),
  659.                             'false']
  660.                         if not v:
  661.                             if q in [
  662.                                 _('no'),
  663.                                 _('unchecked'),
  664.                                 'true']:
  665.                                 matches.add(item[0])
  666.                             
  667.                         q in [
  668.                             _('no'),
  669.                             _('unchecked'),
  670.                             'true']
  671.                         if q in [
  672.                             _('yes'),
  673.                             _('checked'),
  674.                             'true']:
  675.                             matches.add(item[0])
  676.                             continue
  677.                         continue
  678.                     
  679.                     if not item[loc]:
  680.                         if q == 'false':
  681.                             matches.add(item[0])
  682.                             continue
  683.                         continue
  684.                     
  685.                     if q == 'false':
  686.                         continue
  687.                     
  688.                     if q == 'true':
  689.                         if isinstance(item[loc], basestring):
  690.                             if item[loc].strip() == '':
  691.                                 continue
  692.                             
  693.                         
  694.                         matches.add(item[0])
  695.                         continue
  696.                     
  697.                     if col_datatype[loc] == 'rating':
  698.                         if rating_query and rating_query == int(item[loc]):
  699.                             matches.add(item[0])
  700.                             continue
  701.                         continue
  702.                     
  703.                     
  704.                     try:
  705.                         if col_datatype[loc] == 'float':
  706.                             if float(query) == item[loc]:
  707.                                 matches.add(item[0])
  708.                             
  709.                             continue
  710.                         
  711.                         if col_datatype[loc] == 'int':
  712.                             if int(query) == item[loc]:
  713.                                 matches.add(item[0])
  714.                             
  715.                             continue
  716.                     except:
  717.                         continue
  718.  
  719.                     if loc not in exclude_fields:
  720.                         if is_multiple_cols[loc] is not None:
  721.                             vals = item[loc].split(is_multiple_cols[loc])
  722.                         else:
  723.                             vals = [
  724.                                 item[loc]]
  725.                         if _match(q, vals, matchkind):
  726.                             matches.add(item[0])
  727.                             continue
  728.                         
  729.                     _match(q, vals, matchkind)
  730.                 
  731.             
  732.         
  733.         return matches
  734.  
  735.     
  736.     def search(self, query, return_matches = False):
  737.         ans = self.search_getting_ids(query, self.search_restriction)
  738.         if return_matches:
  739.             return ans
  740.         self._map_filtered = ans
  741.  
  742.     
  743.     def search_getting_ids(self, query, search_restriction):
  744.         q = ''
  745.         if not query or not query.strip():
  746.             q = search_restriction
  747.         else:
  748.             q = query
  749.             if search_restriction:
  750.                 q = u'%s (%s)' % (search_restriction, query)
  751.             
  752.         if not q:
  753.             return list(self._map)
  754.         matches = self.parse(q)
  755.         tmap = list(itertools.repeat(False, len(self._data)))
  756.         for x in matches:
  757.             tmap[x] = True
  758.         
  759.         return _[1]
  760.  
  761.     
  762.     def set_search_restriction(self, s):
  763.         self.search_restriction = s
  764.  
  765.     
  766.     def remove(self, id):
  767.         self._data[id] = None
  768.         
  769.         try:
  770.             self._map.remove(id)
  771.         except ValueError:
  772.             pass
  773.  
  774.         
  775.         try:
  776.             self._map_filtered.remove(id)
  777.         except ValueError:
  778.             pass
  779.  
  780.  
  781.     
  782.     def set(self, row, col, val, row_is_id = False):
  783.         id = None if row_is_id else self._map_filtered[row]
  784.         self._data[id][self.FIELD_MAP['all_metadata']] = None
  785.         self._data[id][col] = val
  786.  
  787.     
  788.     def get(self, row, col, row_is_id = False):
  789.         id = None if row_is_id else self._map_filtered[row]
  790.         return self._data[id][col]
  791.  
  792.     
  793.     def index(self, id, cache = False):
  794.         x = None if cache else self._map_filtered
  795.         return x.index(id)
  796.  
  797.     
  798.     def row(self, id):
  799.         return self.index(id)
  800.  
  801.     
  802.     def has_id(self, id):
  803.         
  804.         try:
  805.             return self._data[id] is not None
  806.         except IndexError:
  807.             pass
  808.  
  809.         return False
  810.  
  811.     
  812.     def refresh_ids(self, db, ids):
  813.         for id in ids:
  814.             
  815.             try:
  816.                 self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
  817.                 self._data[id].append(db.has_cover(id, index_is_id = True))
  818.                 self._data[id].append(db.book_on_device_string(id))
  819.                 self._data[id].append(None)
  820.                 if len(self.composites) > 0:
  821.                     mi = db.get_metadata(id, index_is_id = True)
  822.                     for k, c in self.composites:
  823.                         self._data[id][c] = mi.get(k, None)
  824.                     
  825.             continue
  826.             except IndexError:
  827.                 return None
  828.             
  829.  
  830.         
  831.         
  832.         try:
  833.             return map(self.row, ids)
  834.         except ValueError:
  835.             None<EXCEPTION MATCH>IndexError
  836.             None<EXCEPTION MATCH>IndexError
  837.         except:
  838.             None<EXCEPTION MATCH>IndexError
  839.  
  840.  
  841.     
  842.     def books_added(self, ids, db):
  843.         if not ids:
  844.             return None
  845.         self._data.extend(repeat(None, (max(ids) - len(self._data)) + 2))
  846.         for id in ids:
  847.             self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
  848.             self._data[id].append(db.has_cover(id, index_is_id = True))
  849.             self._data[id].append(db.book_on_device_string(id))
  850.             self._data[id].append(None)
  851.             if len(self.composites) > 0:
  852.                 mi = db.get_metadata(id, index_is_id = True)
  853.                 for k, c in self.composites:
  854.                     self._data[id][c] = mi.get(k)
  855.                 
  856.             ids
  857.         
  858.         self._map[0:0] = ids
  859.         self._map_filtered[0:0] = ids
  860.  
  861.     
  862.     def books_deleted(self, ids):
  863.         for id in ids:
  864.             self.remove(id)
  865.         
  866.  
  867.     
  868.     def count(self):
  869.         return len(self._map)
  870.  
  871.     
  872.     def refresh_ondevice(self, db):
  873.         ondevice_col = self.FIELD_MAP['ondevice']
  874.         for item in self._data:
  875.             if item is not None:
  876.                 item[ondevice_col] = db.book_on_device_string(item[0])
  877.                 continue
  878.         
  879.  
  880.     
  881.     def refresh(self, db, field = None, ascending = True):
  882.         temp = db.conn.get('SELECT * FROM meta2')
  883.         self._data = None if temp else []
  884.         for r in temp:
  885.             self._data[r[0]] = r
  886.         
  887.         for item in self._data:
  888.             if item is not None:
  889.                 item.append(db.has_cover(item[0], index_is_id = True))
  890.                 item.append(db.book_on_device_string(item[0]))
  891.                 item.append(None)
  892.                 if len(self.composites) > 0:
  893.                     mi = db.get_metadata(item[0], index_is_id = True)
  894.                     for k, c in self.composites:
  895.                         item[c] = mi.get(k)
  896.                     
  897.                 
  898.             len(self.composites) > 0
  899.         
  900.         self._map = _[1]
  901.         self._map_filtered = list(self._map)
  902.         if self.search_restriction:
  903.             self.search('', return_matches = False)
  904.         
  905.  
  906.     
  907.     def sanitize_sort_field_name(self, field):
  908.         field = self.field_metadata.search_term_to_field_key(field.lower().strip())
  909.         if field == 'title':
  910.             field = 'sort'
  911.         elif field == 'authors':
  912.             field = 'author_sort'
  913.         
  914.         return field
  915.  
  916.     
  917.     def sort(self, field, ascending, subsort = False):
  918.         self.multisort([
  919.             (field, ascending)])
  920.  
  921.     
  922.     def multisort(self, fields = [], subsort = False):
  923.         fields = [ (self.sanitize_sort_field_name(x), bool(y)) for x, y in fields ]
  924.         keys = self.field_metadata.sortable_field_keys()
  925.         fields = _[2]
  926.         keyg = SortKeyGenerator(fields, self.field_metadata, self._data)
  927.         tmap = list(itertools.repeat(False, len(self._data)))
  928.         for x in self._map_filtered:
  929.             tmap[x] = True
  930.         
  931.         self._map_filtered = _[4]
  932.  
  933.  
  934.  
  935. class SortKey(object):
  936.     
  937.     def __init__(self, orders, values):
  938.         self.orders = orders
  939.         self.values = values
  940.  
  941.     
  942.     def __cmp__(self, other):
  943.         for i, ascending in enumerate(self.orders):
  944.             ans = cmp(self.values[i], other.values[i])
  945.             if ans != 0:
  946.                 return ans * ascending
  947.         
  948.         return 0
  949.  
  950.  
  951.  
  952. class SortKeyGenerator(object):
  953.     
  954.     def __init__(self, fields, field_metadata, data):
  955.         self.field_metadata = field_metadata
  956.         self.orders = [ _[1] if x[1] else 1 for x in fields ]
  957.         self.entries = [ (x[0], field_metadata[x[0]]) for x in fields ]
  958.         self.library_order = tweaks['title_series_sorting'] == 'library_order'
  959.         self.data = data
  960.  
  961.     
  962.     def __call__(self, record):
  963.         values = tuple(self.itervals(self.data[record]))
  964.         if len(values) == 1:
  965.             return values[0]
  966.         return SortKey(self.orders, values)
  967.  
  968.     
  969.     def itervals(self, record):
  970.         for name, fm in self.entries:
  971.             dt = fm['datatype']
  972.             val = record[fm['rec_index']]
  973.             if dt == 'datetime':
  974.                 if val is None:
  975.                     val = UNDEFINED_DATE
  976.                 
  977.             elif dt == 'series':
  978.                 if val is None:
  979.                     val = ('', 1)
  980.                 else:
  981.                     val = val.lower()
  982.                     if self.library_order:
  983.                         val = title_sort(val)
  984.                     
  985.                     sidx_fm = self.field_metadata[name + '_index']
  986.                     sidx = record[sidx_fm['rec_index']]
  987.                     val = (val, sidx)
  988.             elif dt in ('text', 'comments', 'composite'):
  989.                 if val is None:
  990.                     val = ''
  991.                 
  992.                 val = val.lower()
  993.             elif dt == 'bool':
  994.                 val = {
  995.                     True: 1,
  996.                     False: 2,
  997.                     None: 3 }.get(val, 3)
  998.             
  999.             yield val
  1000.         
  1001.  
  1002.  
  1003.