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

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2010, Timothy Legge <timlegge at gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
  6. __docformat__ = 'restructuredtext en'
  7. import os
  8. import time
  9. import sqlite3 as sqlite
  10. from calibre.devices.usbms.books import BookList
  11. from calibre.devices.kobo.books import Book
  12. from calibre.devices.kobo.books import ImageWrapper
  13. from calibre.devices.mime import mime_type_ext
  14. from calibre.devices.usbms.driver import USBMS, debug_print
  15. from calibre import prints
  16. from calibre.devices.usbms.books import CollectionsBookList
  17.  
  18. class KOBO(USBMS):
  19.     name = 'Kobo Reader Device Interface'
  20.     gui_name = 'Kobo Reader'
  21.     description = _('Communicate with the Kobo Reader')
  22.     author = 'Timothy Legge and Kovid Goyal'
  23.     version = (1, 0, 7)
  24.     dbversion = 0
  25.     supported_platforms = [
  26.         'windows',
  27.         'osx',
  28.         'linux']
  29.     booklist_class = CollectionsBookList
  30.     FORMATS = [
  31.         'epub',
  32.         'pdf']
  33.     CAN_SET_METADATA = [
  34.         'collections']
  35.     VENDOR_ID = [
  36.         8759]
  37.     PRODUCT_ID = [
  38.         16737]
  39.     BCD = [
  40.         272]
  41.     VENDOR_NAME = [
  42.         'KOBO_INC',
  43.         'KOBO']
  44.     WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = [
  45.         '.KOBOEREADER',
  46.         'EREADER']
  47.     EBOOK_DIR_MAIN = ''
  48.     SUPPORTS_SUB_DIRS = True
  49.     VIRTUAL_BOOK_EXTENSIONS = frozenset([
  50.         'kobo'])
  51.     EXTRA_CUSTOMIZATION_MESSAGE = _('The Kobo supports only one collection currently: the "Im_Reading" list.  Create a tag called "Im_Reading" ') + 'for automatic management'
  52.     EXTRA_CUSTOMIZATION_DEFAULT = ', '.join([
  53.         'tags'])
  54.     
  55.     def initialize(self):
  56.         USBMS.initialize(self)
  57.         self.book_class = Book
  58.  
  59.     
  60.     def books(self, oncard = None, end_session = True):
  61.         path_to_ext = path_to_ext
  62.         import calibre.ebooks.metadata.meta
  63.         dummy_bl = BookList(None, None, None)
  64.         if oncard == 'carda' and not (self._card_a_prefix):
  65.             self.report_progress(1, _('Getting list of books on device...'))
  66.             return dummy_bl
  67.         if oncard == 'cardb' and not (self._card_b_prefix):
  68.             self.report_progress(1, _('Getting list of books on device...'))
  69.             return dummy_bl
  70.         if oncard and oncard != 'carda' and oncard != 'cardb':
  71.             self.report_progress(1, _('Getting list of books on device...'))
  72.             return dummy_bl
  73.         if oncard == 'carda':
  74.             pass
  75.         elif oncard == 'cardb':
  76.             pass
  77.         
  78.         prefix = self._main_prefix
  79.         self.booklist_class.rebuild_collections = self.rebuild_collections
  80.         bl = self.booklist_class(oncard, prefix, self.settings)
  81.         need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
  82.         bl_cache = { }
  83.         for idx, b in enumerate(bl):
  84.             bl_cache[b.lpath] = idx
  85.         
  86.         
  87.         def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus):
  88.             changed = False
  89.             
  90.             try:
  91.                 lpath = path.partition(self.normalize_path(prefix))[2]
  92.                 if lpath.startswith(os.sep):
  93.                     lpath = lpath[len(os.sep):]
  94.                     lpath = lpath.replace('\\', '/')
  95.                 
  96.                 playlist_map = { }
  97.                 if readstatus == 1:
  98.                     playlist_map[lpath] = 'Im_Reading'
  99.                 elif readstatus == 2:
  100.                     playlist_map[lpath] = 'Read'
  101.                 
  102.                 path = self.normalize_path(path)
  103.                 idx = bl_cache.get(lpath, None)
  104.                 if idx is not None:
  105.                     bl_cache[lpath] = None
  106.                     if ImageID is not None:
  107.                         imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed')
  108.                         if imagename is not None:
  109.                             bl[idx].thumbnail = ImageWrapper(imagename)
  110.                         
  111.                     
  112.                     if ContentType != '6' or self.dbversion < 8 or self.dbversion >= 8:
  113.                         if self.update_metadata_item(bl[idx]):
  114.                             changed = True
  115.                         
  116.                     
  117.                     if lpath in playlist_map and playlist_map[lpath] not in bl[idx].device_collections:
  118.                         bl[idx].device_collections.append(playlist_map[lpath])
  119.                     
  120.                 elif ContentType == '6' and self.dbversion < 8:
  121.                     book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size = 1048576)
  122.                 else:
  123.                     
  124.                     try:
  125.                         book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
  126.                     except:
  127.                         debug_print('prefix: ', prefix, 'lpath: ', lpath, 'title: ', title, 'authors: ', authors, 'mime: ', mime, 'date: ', date, 'ContentType: ', ContentType, 'ImageID: ', ImageID)
  128.                         raise 
  129.  
  130.                 book.device_collections = None if lpath in playlist_map else []
  131.                 if bl.add_book(book, replace_metadata = False):
  132.                     changed = True
  133.             except:
  134.                 import traceback
  135.                 traceback.print_exc()
  136.  
  137.             return changed
  138.  
  139.         connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
  140.         cursor = connection.cursor()
  141.         cursor.execute('select version from dbversion')
  142.         result = cursor.fetchone()
  143.         self.dbversion = result[0]
  144.         query = 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ImageID, ReadStatus from content where BookID is Null'
  145.         cursor.execute(query)
  146.         changed = False
  147.         for i, row in enumerate(cursor):
  148.             path = self.path_from_contentid(row[3], row[5], oncard)
  149.             mime = (not (self._card_a_prefix), not (self._card_b_prefix), oncard != 'cardb') if path.find('kepub') == -1 else 'application/epub+zip'
  150.             if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith('file:///mnt/sd/'):
  151.                 changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7])
  152.             elif oncard == 'carda' and row[3].startswith('file:///mnt/sd/'):
  153.                 changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7])
  154.             
  155.             if changed:
  156.                 need_sync = True
  157.                 continue
  158.         
  159.         cursor.close()
  160.         connection.close()
  161.         for idx in sorted(bl_cache.itervalues(), reverse = True):
  162.             if idx is not None:
  163.                 need_sync = True
  164.                 del bl[idx]
  165.                 continue
  166.         
  167.         if need_sync:
  168.             if oncard == 'cardb':
  169.                 self.sync_booklists((None, None, bl))
  170.             elif oncard == 'carda':
  171.                 self.sync_booklists((None, bl, None))
  172.             else:
  173.                 self.sync_booklists((bl, None, None))
  174.         
  175.         self.report_progress(1, _('Getting list of books on device...'))
  176.         return bl
  177.  
  178.     
  179.     def delete_via_sql(self, ContentID, ContentType):
  180.         connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
  181.         cursor = connection.cursor()
  182.         t = (ContentID,)
  183.         cursor.execute('select ImageID from content where ContentID = ?', t)
  184.         ImageID = None
  185.         for row in cursor:
  186.             ImageID = row[0]
  187.         
  188.         cursor.close()
  189.         cursor = connection.cursor()
  190.         if ContentType == 6 and self.dbversion < 8:
  191.             cursor.execute('delete from shortcover_page where shortcoverid in (select ContentID from content where BookID = ?)', t)
  192.         
  193.         cursor.execute('delete from volume_shortcovers where volumeid = ?', t)
  194.         t = (ContentID, ContentID)
  195.         cursor.execute('delete from content where BookID  = ? or ContentID = ?', t)
  196.         connection.commit()
  197.         cursor.close()
  198.         if ImageID == None:
  199.             print 'Error condition ImageID was not found'
  200.             print 'You likely tried to delete a book that the kobo has not yet added to the database'
  201.         
  202.         connection.close()
  203.         return ImageID
  204.  
  205.     
  206.     def delete_images(self, ImageID):
  207.         if ImageID != None:
  208.             path_prefix = '.kobo/images/'
  209.             path = self._main_prefix + path_prefix + ImageID
  210.             file_endings = (' - iPhoneThumbnail.parsed', ' - bbMediumGridList.parsed', ' - NickelBookCover.parsed')
  211.             for ending in file_endings:
  212.                 fpath = path + ending
  213.                 fpath = self.normalize_path(fpath)
  214.                 if os.path.exists(fpath):
  215.                     os.unlink(fpath)
  216.                     continue
  217.             
  218.         
  219.  
  220.     
  221.     def delete_books(self, paths, end_session = True):
  222.         for i, path in enumerate(paths):
  223.             self.report_progress((i + 1) / float(len(paths)), _('Removing books from device...'))
  224.             path = self.normalize_path(path)
  225.             extension = os.path.splitext(path)[1]
  226.             ContentType = None if extension != '' else self.get_content_type_from_path(path)
  227.             ContentID = self.contentid_from_path(path, ContentType)
  228.             ImageID = self.delete_via_sql(ContentID, ContentType)
  229.             self.delete_images(ImageID)
  230.             if os.path.exists(path):
  231.                 os.unlink(path)
  232.                 filepath = os.path.splitext(path)[0]
  233.                 for ext in self.DELETE_EXTS:
  234.                     if os.path.exists(filepath + ext):
  235.                         os.unlink(filepath + ext)
  236.                     
  237.                     if os.path.exists(path + ext):
  238.                         os.unlink(path + ext)
  239.                         continue
  240.                 
  241.                 if self.SUPPORTS_SUB_DIRS:
  242.                     
  243.                     try:
  244.                         os.removedirs(os.path.dirname(path))
  245.  
  246.                 
  247.             self.SUPPORTS_SUB_DIRS
  248.         
  249.         self.report_progress(1, _('Removing books from device...'))
  250.  
  251.     
  252.     def remove_books_from_metadata(self, paths, booklists):
  253.         for i, path in enumerate(paths):
  254.             self.report_progress((i + 1) / float(len(paths)), _('Removing books from device metadata listing...'))
  255.             for bl in booklists:
  256.                 for book in bl:
  257.                     if path.endswith(book.path):
  258.                         bl.remove_book(book)
  259.                         continue
  260.                 
  261.             
  262.         
  263.         self.report_progress(1, _('Removing books from device metadata listing...'))
  264.  
  265.     
  266.     def add_books_to_metadata(self, locations, metadata, booklists):
  267.         metadata = iter(metadata)
  268.         for i, location in enumerate(locations):
  269.             self.report_progress((i + 1) / float(len(locations)), _('Adding books to device metadata listing...'))
  270.             info = metadata.next()
  271.             if location[1] == 'cardb':
  272.                 pass
  273.             elif location[1] == 'carda':
  274.                 pass
  275.             
  276.             blist = 0
  277.             path = self.normalize_path(location[0])
  278.             if self._main_prefix:
  279.                 prefix = None if path.startswith(self.normalize_path(self._main_prefix)) else None
  280.             
  281.             if not prefix and self._card_a_prefix:
  282.                 prefix = None if path.startswith(self.normalize_path(self._card_a_prefix)) else None
  283.             
  284.             if not prefix and self._card_b_prefix:
  285.                 prefix = None if path.startswith(self.normalize_path(self._card_b_prefix)) else None
  286.             
  287.             if prefix is None:
  288.                 prints('in add_books_to_metadata. Prefix is None!', path, self._main_prefix)
  289.                 continue
  290.             
  291.             lpath = path.partition(prefix)[2]
  292.             if lpath.startswith('/') or lpath.startswith('\\'):
  293.                 lpath = lpath[1:]
  294.             
  295.             book = Book(prefix, lpath, '', '', '', '', '', '', other = info)
  296.             if book.size is None:
  297.                 book.size = os.stat(self.normalize_path(path)).st_size
  298.             
  299.             b = booklists[blist].add_book(book, replace_metadata = True)
  300.             if b:
  301.                 b._new_book = True
  302.                 continue
  303.         
  304.         self.report_progress(1, _('Adding books to device metadata listing...'))
  305.  
  306.     
  307.     def contentid_from_path(self, path, ContentType):
  308.         if ContentType == 6:
  309.             if self.dbversion < 8:
  310.                 ContentID = os.path.splitext(path)[0]
  311.                 ContentID = ContentID.replace(self._main_prefix, '')
  312.             else:
  313.                 ContentID = path
  314.                 ContentID = ContentID.replace(self._main_prefix + '.kobo/kepub/', '')
  315.             if self._card_a_prefix is not None:
  316.                 ContentID = ContentID.replace(self._card_a_prefix, '')
  317.             
  318.         elif ContentType == 999:
  319.             ContentID = path
  320.             ContentID = ContentID.replace(self._main_prefix, '/mnt/onboard/')
  321.             if self._card_a_prefix is not None:
  322.                 ContentID = ContentID.replace(self._card_a_prefix, '/mnt/sd/')
  323.             
  324.         else:
  325.             ContentID = path
  326.             ContentID = ContentID.replace(self._main_prefix, 'file:///mnt/onboard/')
  327.             if self._card_a_prefix is not None:
  328.                 ContentID = ContentID.replace(self._card_a_prefix, 'file:///mnt/sd/')
  329.             
  330.         ContentID = ContentID.replace('\\', '/')
  331.         return ContentID
  332.  
  333.     
  334.     def get_content_type_from_path(self, path):
  335.         if path.find('kepub') >= 0:
  336.             ContentType = 6
  337.         
  338.         return ContentType
  339.  
  340.     
  341.     def get_content_type_from_extension(self, extension):
  342.         if extension == '.kobo':
  343.             ContentType = 6
  344.         elif extension == '.pdf' or extension == '.epub':
  345.             ContentType = 16
  346.         else:
  347.             ContentType = 999
  348.         return ContentType
  349.  
  350.     
  351.     def path_from_contentid(self, ContentID, ContentType, oncard):
  352.         path = ContentID
  353.         if oncard == 'cardb':
  354.             print 'path from_contentid cardb'
  355.         elif oncard == 'carda':
  356.             path = path.replace('file:///mnt/sd/', self._card_a_prefix)
  357.         elif ContentType == '6' and self.dbversion < 8:
  358.             path = self._main_prefix + path + '.kobo'
  359.         elif (ContentType == '6' or ContentType == '10') and self.dbversion >= 8:
  360.             path = self._main_prefix + '.kobo/kepub/' + path
  361.         else:
  362.             path = path.replace('file:///mnt/onboard/', self._main_prefix)
  363.             path = path.replace('/mnt/onboard/', self._main_prefix)
  364.         return path
  365.  
  366.     
  367.     def get_file(self, path, *args, **kwargs):
  368.         tpath = self.munge_path(path)
  369.         extension = os.path.splitext(tpath)[1]
  370.         if extension == '.kobo':
  371.             UserFeedback = UserFeedback
  372.             import calibre.devices.errors
  373.             raise UserFeedback(_('Not Implemented'), _('".kobo" files do not exist on the device as books instead, they are rows in the sqlite database. Currently they cannot be exported or viewed.'), UserFeedback.WARN)
  374.         extension == '.kobo'
  375.         return USBMS.get_file(self, path, *args, **kwargs)
  376.  
  377.     
  378.     def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID):
  379.         MetaInformation = MetaInformation
  380.         import calibre.ebooks.metadata
  381.         if cls.settings().read_metadata or cls.MUST_READ_METADATA:
  382.             mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
  383.         else:
  384.             metadata_from_filename = metadata_from_filename
  385.             import calibre.ebooks.metadata.meta
  386.             mi = metadata_from_filename(cls.normalize_path(os.path.basename(lpath)), cls.build_template_regexp())
  387.         if mi is None:
  388.             mi = MetaInformation(os.path.splitext(os.path.basename(lpath))[0], [
  389.                 _('Unknown')])
  390.         
  391.         size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size
  392.         book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size = size, other = mi)
  393.         return book
  394.  
  395.     book_from_path = classmethod(book_from_path)
  396.     
  397.     def get_device_paths(self):
  398.         paths = { }
  399.         prefixes = { }
  400.         for prefix, path, source_id in [
  401.             ('main', 'metadata.calibre', 0),
  402.             ('card_a', 'metadata.calibre', 1),
  403.             ('card_b', 'metadata.calibre', 2)]:
  404.             prefix = getattr(self, '_%s_prefix' % prefix)
  405.             if prefix is not None and os.path.exists(prefix):
  406.                 paths[source_id] = os.path.join(prefix, *path.split('/'))
  407.                 continue
  408.         
  409.         return paths
  410.  
  411.     
  412.     def update_device_database_collections(self, booklists, collections_attributes, oncard):
  413.         collections_attributes = [
  414.             'tags']
  415.         collections = booklists.get_collections(collections_attributes)
  416.         connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
  417.         cursor = connection.cursor()
  418.         if collections:
  419.             for category, books in collections.items():
  420.                 if category == 'Im_Reading':
  421.                     if oncard == 'carda':
  422.                         query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ReadStatus = 1 and ContentID like 'file:///mnt/sd/%'"
  423.                     elif oncard != 'carda' and oncard != 'cardb':
  424.                         query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ReadStatus = 1 and ContentID not like 'file:///mnt/sd/%'"
  425.                     
  426.                     
  427.                     try:
  428.                         cursor.execute(query)
  429.                     except:
  430.                         debug_print('Database Exception:  Unable to reset Im_Reading list')
  431.                         raise 
  432.  
  433.                     connection.commit()
  434.                     for book in books:
  435.                         book.device_collections = [
  436.                             'Im_Reading']
  437.                         extension = os.path.splitext(book.path)[1]
  438.                         ContentType = None if extension != '' else self.get_content_type_from_path(book.path)
  439.                         ContentID = self.contentid_from_path(book.path, ContentType)
  440.                         datelastread = time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime())
  441.                         t = (datelastread, ContentID)
  442.                         
  443.                         try:
  444.                             cursor.execute("update content set ReadStatus=1,FirstTimeReading='false',DateLastRead=? where BookID is Null and ContentID = ?", t)
  445.                         except:
  446.                             debug_print('Database Exception:  Unable create Im_Reading list')
  447.                             raise 
  448.                             continue
  449.  
  450.                         connection.commit()
  451.                     
  452.                 
  453.                 if category == 'Read':
  454.                     if oncard == 'carda':
  455.                         query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ReadStatus = 2 and ContentID like 'file:///mnt/sd/%'"
  456.                     elif oncard != 'carda' and oncard != 'cardb':
  457.                         query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ReadStatus = 2 and ContentID not like 'file:///mnt/sd/%'"
  458.                     
  459.                     
  460.                     try:
  461.                         cursor.execute(query)
  462.                     except:
  463.                         debug_print('Database Exception:  Unable to reset Im_Reading list')
  464.                         raise 
  465.  
  466.                     connection.commit()
  467.                     for book in books:
  468.                         book.device_collections = [
  469.                             'Read']
  470.                         extension = os.path.splitext(book.path)[1]
  471.                         ContentType = None if extension != '' else self.get_content_type_from_path(book.path)
  472.                         ContentID = self.contentid_from_path(book.path, ContentType)
  473.                         t = (ContentID,)
  474.                         
  475.                         try:
  476.                             cursor.execute("update content set ReadStatus=2,FirstTimeReading='true' where BookID is Null and ContentID = ?", t)
  477.                         except:
  478.                             debug_print('Database Exception:  Unable set book as Rinished')
  479.                             raise 
  480.                             continue
  481.  
  482.                         connection.commit()
  483.                     
  484.             
  485.         else:
  486.             print 'Reseting ReadStatus to 0'
  487.             if oncard == 'carda':
  488.                 query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ContentID like 'file:///mnt/sd/%'"
  489.             elif oncard != 'carda' and oncard != 'cardb':
  490.                 query = "update content set ReadStatus=0, FirstTimeReading = 'true' where BookID is Null and ContentID not like 'file:///mnt/sd/%'"
  491.             
  492.             
  493.             try:
  494.                 cursor.execute(query)
  495.             except:
  496.                 debug_print('Database Exception:  Unable to reset Im_Reading list')
  497.                 raise 
  498.  
  499.             connection.commit()
  500.         cursor.close()
  501.         connection.close()
  502.  
  503.     
  504.     def sync_booklists(self, booklists, end_session = True):
  505.         paths = self.get_device_paths()
  506.         blists = { }
  507.         for i in paths:
  508.             if booklists[i] is not None:
  509.                 blists[i] = booklists[i]
  510.                 continue
  511.         
  512.         opts = self.settings()
  513.         for i, blist in blists.items():
  514.             if i == 0:
  515.                 oncard = 'main'
  516.             else:
  517.                 oncard = 'carda'
  518.             self.update_device_database_collections(blist, collections, oncard)
  519.         
  520.         USBMS.sync_booklists(self, booklists, end_session = end_session)
  521.  
  522.     
  523.     def rebuild_collections(self, booklist, oncard):
  524.         collections_attributes = []
  525.         self.update_device_database_collections(booklist, collections_attributes, oncard)
  526.  
  527.  
  528.