home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_956 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  16.9 KB  |  554 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL 3'
  6. __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
  7. __docformat__ = 'restructuredtext en'
  8. import traceback
  9. import sys
  10. import textwrap
  11. import re
  12. from threading import Thread
  13. from calibre import prints
  14. from calibre.utils.config import OptionParser
  15. from calibre.utils.logging import default_log
  16. from calibre.customize import Plugin
  17. from calibre.ebooks.metadata.covers import check_for_cover
  18. metadata_config = None
  19.  
  20. class MetadataSource(Plugin):
  21.     author = 'Kovid Goyal'
  22.     supported_platforms = [
  23.         'windows',
  24.         'osx',
  25.         'linux']
  26.     metadata_type = 'basic'
  27.     string_customization_help = None
  28.     type = _('Metadata download')
  29.     
  30.     def __call__(self, title, author, publisher, isbn, verbose, log = None, extra = None):
  31.         self.worker = Thread(target = self._fetch)
  32.         self.worker.daemon = True
  33.         self.title = title
  34.         self.verbose = verbose
  35.         self.book_author = author
  36.         self.publisher = publisher
  37.         self.isbn = isbn
  38.         self.log = None if log is not None else default_log
  39.         self.extra = extra
  40.         self.exception = None
  41.         self.tb = None
  42.         self.results = []
  43.         self.worker.start()
  44.  
  45.     
  46.     def _fetch(self):
  47.         
  48.         try:
  49.             self.fetch()
  50.             if self.results:
  51.                 c = self.config_store().get(self.name, { })
  52.                 res = self.results
  53.                 if hasattr(res, 'authors'):
  54.                     res = [
  55.                         res]
  56.                 
  57.                 for mi in res:
  58.                     if not c.get('rating', True):
  59.                         mi.rating = None
  60.                     
  61.                     if not c.get('comments', True):
  62.                         mi.comments = None
  63.                     
  64.                     if not c.get('tags', True):
  65.                         mi.tags = []
  66.                         continue
  67.                 
  68.         except Exception:
  69.             e = None
  70.             self.exception = e
  71.             self.tb = traceback.format_exc()
  72.  
  73.  
  74.     
  75.     def fetch(self):
  76.         raise NotImplementedError
  77.  
  78.     
  79.     def join(self):
  80.         return self.worker.join()
  81.  
  82.     
  83.     def is_customizable(self):
  84.         return True
  85.  
  86.     
  87.     def config_store(self):
  88.         global metadata_config
  89.         if metadata_config is None:
  90.             XMLConfig = XMLConfig
  91.             import calibre.utils.config
  92.             metadata_config = XMLConfig('plugins/metadata_download')
  93.         
  94.         return metadata_config
  95.  
  96.     
  97.     def config_widget(self):
  98.         QWidget = QWidget
  99.         QVBoxLayout = QVBoxLayout
  100.         QLabel = QLabel
  101.         Qt = Qt
  102.         QLineEdit = QLineEdit
  103.         QCheckBox = QCheckBox
  104.         import PyQt4.Qt
  105.         config = config
  106.         import calibre.customize.ui
  107.         w = QWidget()
  108.         w._layout = QVBoxLayout(w)
  109.         w.setLayout(w._layout)
  110.         if self.string_customization_help is not None:
  111.             w._sc_label = QLabel(self.string_customization_help, w)
  112.             w._layout.addWidget(w._sc_label)
  113.             customization = config['plugin_customization']
  114.             def_sc = customization.get(self.name, '')
  115.             if not def_sc:
  116.                 def_sc = ''
  117.             
  118.             w._sc = QLineEdit(def_sc, w)
  119.             w._layout.addWidget(w._sc)
  120.             w._sc_label.setWordWrap(True)
  121.             w._sc_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
  122.             w._sc_label.setOpenExternalLinks(True)
  123.         
  124.         c = self.config_store()
  125.         c = c.get(self.name, { })
  126.         for x, l in {
  127.             'rating': _('ratings'),
  128.             'tags': _('tags'),
  129.             'comments': _('description/reviews') }.items():
  130.             cb = QCheckBox(_('Download %s from %s') % (l, self.name))
  131.             setattr(w, '_' + x, cb)
  132.             cb.setChecked(c.get(x, True))
  133.             w._layout.addWidget(cb)
  134.         
  135.         return w
  136.  
  137.     
  138.     def save_settings(self, w):
  139.         dl_settings = { }
  140.         for x in ('rating', 'tags', 'comments'):
  141.             dl_settings[x] = getattr(w, '_' + x).isChecked()
  142.         
  143.         c = self.config_store()
  144.         c.set(self.name, dl_settings)
  145.         if hasattr(w, '_sc'):
  146.             sc = unicode(w._sc.text()).strip()
  147.             customize_plugin = customize_plugin
  148.             import calibre.customize.ui
  149.             customize_plugin(self, sc)
  150.         
  151.  
  152.     
  153.     def customization_help(self):
  154.         return 'This plugin can only be customized using the GUI'
  155.  
  156.  
  157.  
  158. class GoogleBooks(MetadataSource):
  159.     name = 'Google Books'
  160.     description = _('Downloads metadata from Google Books')
  161.     
  162.     def fetch(self):
  163.         search = search
  164.         import calibre.ebooks.metadata.google_books
  165.         
  166.         try:
  167.             self.results = search(self.title, self.book_author, self.publisher, self.isbn, max_results = 10, verbose = self.verbose)
  168.         except Exception:
  169.             e = None
  170.             self.exception = e
  171.             self.tb = traceback.format_exc()
  172.  
  173.  
  174.  
  175.  
  176. class ISBNDB(MetadataSource):
  177.     name = 'IsbnDB'
  178.     description = _('Downloads metadata from isbndb.com')
  179.     
  180.     def fetch(self):
  181.         if not self.site_customization:
  182.             return None
  183.         option_parser = option_parser
  184.         create_books = create_books
  185.         import calibre.ebooks.metadata.isbndb
  186.         args = [
  187.             'isbndb']
  188.         if self.isbn:
  189.             args.extend([
  190.                 '--isbn',
  191.                 self.isbn])
  192.         elif self.title:
  193.             args.extend([
  194.                 '--title',
  195.                 self.title])
  196.         
  197.         if self.book_author:
  198.             args.extend([
  199.                 '--author',
  200.                 self.book_author])
  201.         
  202.         if self.publisher:
  203.             args.extend([
  204.                 '--publisher',
  205.                 self.publisher])
  206.         
  207.         if self.verbose:
  208.             args.extend([
  209.                 '--verbose'])
  210.         
  211.         args.append(self.site_customization)
  212.         
  213.         try:
  214.             (opts, args) = option_parser().parse_args(args)
  215.             self.results = create_books(opts, args)
  216.         except Exception:
  217.             e = None
  218.             self.exception = e
  219.             self.tb = traceback.format_exc()
  220.  
  221.  
  222.     
  223.     def string_customization_help(self):
  224.         ans = _('To use isbndb.com you must sign up for a %sfree account%s and enter your access key below.')
  225.         return '<p>' + ans % ('<a href="http://www.isbndb.com">', '</a>')
  226.  
  227.     string_customization_help = property(string_customization_help)
  228.  
  229.  
  230. class Amazon(MetadataSource):
  231.     name = 'Amazon'
  232.     metadata_type = 'social'
  233.     description = _('Downloads social metadata from amazon.com')
  234.     
  235.     def fetch(self):
  236.         if not self.isbn:
  237.             return None
  238.         get_social_metadata = get_social_metadata
  239.         import calibre.ebooks.metadata.amazon
  240.         
  241.         try:
  242.             self.results = get_social_metadata(self.title, self.book_author, self.publisher, self.isbn)
  243.         except Exception:
  244.             self.isbn
  245.             e = self.isbn
  246.             self.exception = e
  247.             self.tb = traceback.format_exc()
  248.         except:
  249.             self.isbn
  250.  
  251.  
  252.  
  253.  
  254. class LibraryThing(MetadataSource):
  255.     name = 'LibraryThing'
  256.     metadata_type = 'social'
  257.     description = _('Downloads series/tags/rating information from librarything.com')
  258.     
  259.     def fetch(self):
  260.         if not self.isbn:
  261.             return None
  262.         get_social_metadata = get_social_metadata
  263.         import calibre.ebooks.metadata.library_thing
  264.         
  265.         try:
  266.             self.results = get_social_metadata(self.title, self.book_author, self.publisher, self.isbn)
  267.         except Exception:
  268.             self.isbn
  269.             e = self.isbn
  270.             self.exception = e
  271.             self.tb = traceback.format_exc()
  272.         except:
  273.             self.isbn
  274.  
  275.  
  276.  
  277.  
  278. def result_index(source, result):
  279.     if not result.isbn:
  280.         return -1
  281.     for i, x in enumerate(source):
  282.         if x.isbn == result.isbn:
  283.             return i
  284.     
  285.     return -1
  286.  
  287.  
  288. def merge_results(one, two):
  289.     for x in two:
  290.         idx = result_index(one, x)
  291.         if idx < 0:
  292.             one.append(x)
  293.             continue
  294.         one[idx].smart_update(x)
  295.     
  296.  
  297.  
  298. class MetadataSources(object):
  299.     
  300.     def __init__(self, sources):
  301.         self.sources = sources
  302.  
  303.     
  304.     def __enter__(self):
  305.         for s in self.sources:
  306.             s.__enter__()
  307.         
  308.         return self
  309.  
  310.     
  311.     def __exit__(self, *args):
  312.         for s in self.sources:
  313.             s.__exit__()
  314.         
  315.  
  316.     
  317.     def __call__(self, *args, **kwargs):
  318.         for s in self.sources:
  319.             s(*args, **kwargs)
  320.         
  321.  
  322.     
  323.     def join(self):
  324.         for s in self.sources:
  325.             s.join()
  326.         
  327.  
  328.  
  329.  
  330. def filter_metadata_results(item):
  331.     keywords = [
  332.         'audio',
  333.         'tape',
  334.         'cassette',
  335.         'abridged',
  336.         'playaway']
  337.     for keyword in keywords:
  338.         if item.publisher and keyword in item.publisher.lower():
  339.             return False
  340.     
  341.     return True
  342.  
  343.  
  344. def do_cover_check(item):
  345.     item.has_cover = False
  346.     
  347.     try:
  348.         item.has_cover = check_for_cover(item)
  349.     except:
  350.         pass
  351.  
  352.  
  353.  
  354. def check_for_covers(items):
  355.     threads = [ Thread(target = do_cover_check, args = (item,)) for item in items ]
  356.     for t in threads:
  357.         t.start()
  358.     
  359.     for t in threads:
  360.         t.join()
  361.     
  362.  
  363.  
  364. def search(title = None, author = None, publisher = None, isbn = None, isbndb_key = None, verbose = 0):
  365.     metadata_sources = metadata_sources
  366.     migrate_isbndb_key = migrate_isbndb_key
  367.     import calibre.customize.ui
  368.     migrate_isbndb_key()
  369.     if isbn is not None:
  370.         isbn = re.sub('[^a-zA-Z0-9]', '', isbn).upper()
  371.     
  372.     fetchers = list(metadata_sources(isbndb_key = isbndb_key))
  373.     
  374.     try:
  375.         manager = _[1]
  376.         manager(title, author, publisher, isbn, verbose)
  377.         manager.join()
  378.     finally:
  379.         pass
  380.  
  381.     results = list(fetchers[0].results)
  382.     for fetcher in fetchers[1:]:
  383.         merge_results(results, fetcher.results)
  384.     
  385.     results = list(filter(filter_metadata_results, results))
  386.     check_for_covers(results)
  387.     words = ('the', 'a', 'an', 'of', 'and')
  388.     prefix_pat = re.compile('^(%s)\\s+' % '|'.join(words))
  389.     trailing_paren_pat = re.compile('\\(.*\\)$')
  390.     whitespace_pat = re.compile('\\s+')
  391.     
  392.     def sort_func(x, y):
  393.         
  394.         def cleanup_title(s):
  395.             if s is None:
  396.                 s = _('Unknown')
  397.             
  398.             s = s.strip().lower()
  399.             s = prefix_pat.sub(' ', s)
  400.             s = trailing_paren_pat.sub('', s)
  401.             s = whitespace_pat.sub(' ', s)
  402.             return s.strip()
  403.  
  404.         t = cleanup_title(title)
  405.         x_title = cleanup_title(x.title)
  406.         y_title = cleanup_title(y.title)
  407.         tx = cmp(t, x_title)
  408.         ty = cmp(t, y_title)
  409.         result = (None, None, None) if abs(tx) == abs(ty) else abs(tx) - abs(ty)
  410.         if result == 0:
  411.             result = -cmp(x.has_cover, y.has_cover)
  412.         
  413.         if result == 0:
  414.             cx = None(len if x.comments else '')
  415.             cy = None(len if y.comments else '')
  416.             t = (cx + cy) / 20
  417.             result = cy - cx
  418.             if abs(result) < t:
  419.                 result = 0
  420.             
  421.         
  422.         return result
  423.  
  424.     results = sorted(results, cmp = sort_func)
  425.     if len(results) > 1:
  426.         if not (results[0].comments) or len(results[0].comments) == 0:
  427.             for r in results[1:]:
  428.                 
  429.                 try:
  430.                     if title and title.lower() == r.title[:len(title)].lower() and r.comments and len(r.comments):
  431.                         results[0].comments = r.comments
  432.                         break
  433.                 continue
  434.                 continue
  435.  
  436.             
  437.         
  438.         pubdate = None
  439.         for r in results:
  440.             if r.pubdate is not None:
  441.                 pubdate = r.pubdate
  442.                 break
  443.                 continue
  444.         
  445.         if pubdate is not None:
  446.             for r in results:
  447.                 if r.pubdate is None:
  448.                     r.pubdate = pubdate
  449.                     continue
  450.             
  451.         
  452.     
  453.     return ([], [ (x.name, x.exception, x.tb) for x in fetchers ])
  454.  
  455.  
  456. def get_social_metadata(mi, verbose = 0):
  457.     metadata_sources = metadata_sources
  458.     import calibre.customize.ui
  459.     fetchers = list(metadata_sources(metadata_type = 'social'))
  460.     
  461.     try:
  462.         manager = _[1]
  463.         manager(mi.title, mi.authors, mi.publisher, mi.isbn, verbose)
  464.         manager.join()
  465.     finally:
  466.         pass
  467.  
  468.     (ratings, tags, comments, series, series_index) = ([], set([]), set([]), None, None)
  469.     for fetcher in fetchers:
  470.         if fetcher.results:
  471.             dmi = fetcher.results
  472.             if dmi.tags:
  473.                 for t in dmi.tags:
  474.                     tags.add(t)
  475.                 
  476.             
  477.             if mi.pubdate is None and dmi.pubdate is not None:
  478.                 mi.pubdate = dmi.pubdate
  479.             
  480.             if dmi.comments:
  481.                 comments.add(dmi.comments)
  482.             
  483.             if dmi.series is not None:
  484.                 series = dmi.series
  485.                 if dmi.series_index is not None:
  486.                     series_index = dmi.series_index
  487.                 
  488.             
  489.         dmi.series is not None
  490.     
  491.     if ratings:
  492.         rating = sum(ratings) / float(len(ratings))
  493.         if mi.rating is None or mi.rating < 0.1:
  494.             mi.rating = rating
  495.         else:
  496.             mi.rating = (mi.rating + rating) / 2
  497.     
  498.     if tags:
  499.         if not mi.tags:
  500.             mi.tags = []
  501.         
  502.         mi.tags += list(tags)
  503.         mi.tags = list(sorted(list(set(mi.tags))))
  504.     
  505.     if comments:
  506.         if not (mi.comments) or len(mi.comments) + 20 < len(' '.join(comments)):
  507.             mi.comments = ''
  508.             for x in comments:
  509.                 mi.comments += x + '\n\n'
  510.             
  511.         
  512.     
  513.     if series and series_index is not None:
  514.         mi.series = series
  515.         mi.series_index = series_index
  516.     
  517.     return _[2]
  518.  
  519.  
  520. def option_parser():
  521.     parser = OptionParser(textwrap.dedent('        %prog [options]\n\n        Fetch book metadata from online sources. You must specify at least one\n        of title, author, publisher or ISBN. If you specify ISBN, the others\n        are ignored.\n        '))
  522.     parser.add_option('-t', '--title', help = 'Book title')
  523.     parser.add_option('-a', '--author', help = 'Book author(s)')
  524.     parser.add_option('-p', '--publisher', help = 'Book publisher')
  525.     parser.add_option('-i', '--isbn', help = 'Book ISBN')
  526.     parser.add_option('-m', '--max-results', default = 10, help = 'Maximum number of results to fetch')
  527.     parser.add_option('-k', '--isbndb-key', help = "The access key for your ISBNDB.com account. Only needed if you want to search isbndb.com and you haven't customized the IsbnDB plugin.")
  528.     parser.add_option('-v', '--verbose', default = 0, action = 'count', help = 'Be more verbose about errors')
  529.     return parser
  530.  
  531.  
  532. def main(args = sys.argv):
  533.     parser = option_parser()
  534.     (opts, args) = parser.parse_args(args)
  535.     (results, exceptions) = search(opts.title, opts.author, opts.publisher, opts.isbn, opts.isbndb_key, opts.verbose)
  536.     social_exceptions = []
  537.     for result in results:
  538.         social_exceptions.extend(get_social_metadata(result, opts.verbose))
  539.         prints(unicode(result))
  540.         print 
  541.     
  542.     for name, exception, tb in exceptions + social_exceptions:
  543.         if exception is not None:
  544.             print 'WARNING: Fetching from', name, 'failed with error:'
  545.             print exception
  546.             print tb
  547.             continue
  548.     
  549.     return 0
  550.  
  551. if __name__ == '__main__':
  552.     sys.exit(main())
  553.  
  554.