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

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
  6. __docformat__ = 'restructuredtext en'
  7. import os
  8. import time
  9. from base64 import b64decode
  10. from uuid import uuid4
  11. from lxml import etree
  12. from datetime import date
  13. from calibre import prints, guess_type, isbytestring
  14. from calibre.devices.errors import DeviceError
  15. from calibre.devices.usbms.driver import debug_print
  16. from calibre.constants import DEBUG, preferred_encoding
  17. from calibre.ebooks.chardet import xml_to_unicode
  18. from calibre.ebooks.metadata import authors_to_string, title_sort, authors_to_sort_string
  19. EMPTY_CARD_CACHE = '<?xml version="1.0" encoding="UTF-8"?>\n<cache xmlns="http://www.kinoma.com/FskCache/1">\n</cache>\n'
  20. EMPTY_EXT_CACHE = '<?xml version="1.0" encoding="UTF-8"?>\n<cacheExt xmlns="http://www.sony.com/xmlns/product/prs/device/1">\n</cacheExt>\n'
  21. MIME_MAP = {
  22.     'lrf': 'application/x-sony-bbeb',
  23.     'lrx': 'application/x-sony-bbeb',
  24.     'rtf': 'application/rtf',
  25.     'pdf': 'application/pdf',
  26.     'txt': 'text/plain',
  27.     'epub': 'application/epub+zip' }
  28. DAY_MAP = dict(Sun = 0, Mon = 1, Tue = 2, Wed = 3, Thu = 4, Fri = 5, Sat = 6)
  29. MONTH_MAP = dict(Jan = 1, Feb = 2, Mar = 3, Apr = 4, May = 5, Jun = 6, Jul = 7, Aug = 8, Sep = 9, Oct = 10, Nov = 11, Dec = 12)
  30. INVERSE_DAY_MAP = dict(zip(DAY_MAP.values(), DAY_MAP.keys()))
  31. INVERSE_MONTH_MAP = dict(zip(MONTH_MAP.values(), MONTH_MAP.keys()))
  32.  
  33. def strptime(src):
  34.     src = src.strip()
  35.     src = src.split()
  36.     src[0] = str(DAY_MAP[src[0][:-1]]) + ','
  37.     src[2] = str(MONTH_MAP[src[2]])
  38.     return time.strptime(' '.join(src), '%w, %d %m %Y %H:%M:%S %Z')
  39.  
  40.  
  41. def strftime(epoch, zone = time.localtime):
  42.     
  43.     try:
  44.         src = time.strftime('%w, %d %m %Y %H:%M:%S GMT', zone(epoch)).split()
  45.     except:
  46.         src = time.strftime('%w, %d %m %Y %H:%M:%S GMT', zone()).split()
  47.  
  48.     src[0] = INVERSE_DAY_MAP[int(src[0][:-1])] + ','
  49.     src[2] = INVERSE_MONTH_MAP[int(src[2])]
  50.     return ' '.join(src)
  51.  
  52.  
  53. def uuid():
  54.     return str(uuid4()).replace('-', '', 1).upper()
  55.  
  56.  
  57. class XMLCache(object):
  58.     
  59.     def __init__(self, paths, ext_paths, prefixes, use_author_sort):
  60.         if DEBUG:
  61.             debug_print('Building XMLCache...', paths)
  62.         
  63.         self.paths = paths
  64.         self.prefixes = prefixes
  65.         self.use_author_sort = use_author_sort
  66.         parser = etree.XMLParser(recover = True)
  67.         self.roots = { }
  68.         for source_id, path in paths.items():
  69.             self.roots[source_id] = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats = True, assume_utf8 = True, verbose = DEBUG)[0], parser = parser)
  70.             if self.roots[source_id] is None:
  71.                 raise Exception('The SONY database at %r is corrupted. Try  disconnecting and reconnecting your reader.' % path)
  72.             self.roots[source_id] is None
  73.         
  74.         self.ext_paths = { }
  75.         self.ext_roots = { }
  76.         for source_id, path in ext_paths.items():
  77.             if os.access(path, os.W_OK):
  78.                 
  79.                 try:
  80.                     
  81.                     try:
  82.                         f = _[4]
  83.                         self.ext_roots[source_id] = etree.fromstring(xml_to_unicode(f.read(), strip_encoding_pats = True, assume_utf8 = True, verbose = DEBUG)[0], parser = parser)
  84.                         self.ext_paths[source_id] = path
  85.                     finally:
  86.                         pass
  87.  
  88.  
  89.                 continue
  90.         
  91.         recs = self.roots[0].xpath('//*[local-name()="records"]')
  92.         if not recs:
  93.             raise DeviceError('The SONY XML database is corrupted (no <records>). Try disconnecting an reconnecting your reader.')
  94.         recs
  95.         self.record_roots = { }
  96.         self.record_roots.update(self.roots)
  97.         self.record_roots[0] = recs[0]
  98.         self.detect_namespaces()
  99.         debug_print('Done building XMLCache...')
  100.  
  101.     
  102.     def purge_broken_playlist_items(self, root):
  103.         id_map = self.build_id_map(root)
  104.         for pl in root.xpath('//*[local-name()="playlist"]'):
  105.             seen = set([])
  106.             for item in list(pl):
  107.                 id_ = item.get('id', None)
  108.                 if id_ is None and id_ in seen or id_map.get(id_, None) is None:
  109.                     if DEBUG:
  110.                         if id_ is None:
  111.                             cause = 'invalid id'
  112.                         elif id_ in seen:
  113.                             cause = 'duplicate item'
  114.                         else:
  115.                             cause = 'id not found'
  116.                         prints('Purging broken playlist item:', id_, 'from playlist:', pl.get('title', None), 'because:', cause)
  117.                     
  118.                     item.getparent().remove(item)
  119.                     continue
  120.                 
  121.                 seen.add(id_)
  122.             
  123.         
  124.  
  125.     
  126.     def prune_empty_playlists(self):
  127.         for i, root in self.record_roots.items():
  128.             self.purge_broken_playlist_items(root)
  129.             for playlist in root.xpath('//*[local-name()="playlist"]'):
  130.                 if len(playlist) == 0 or not playlist.get('title', None):
  131.                     if DEBUG:
  132.                         debug_print('Removing playlist id:', playlist.get('id', None), playlist.get('title', None))
  133.                     
  134.                     playlist.getparent().remove(playlist)
  135.                     continue
  136.             
  137.         
  138.  
  139.     
  140.     def ensure_unique_playlist_titles(self):
  141.         for i, root in self.record_roots.items():
  142.             seen = set([])
  143.             for playlist in root.xpath('//*[local-name()="playlist"]'):
  144.                 title = playlist.get('title', None)
  145.                 if title is None:
  146.                     title = _('Unnamed')
  147.                     playlist.set('title', title)
  148.                 
  149.                 if title in seen:
  150.                     for i in range(2, 1000):
  151.                         if title + str(i) not in seen:
  152.                             title = title + str(i)
  153.                             playlist.set('title', title)
  154.                             seen.add(title)
  155.                             break
  156.                             continue
  157.                     
  158.                 seen.add(title)
  159.             
  160.         
  161.  
  162.     
  163.     def build_id_playlist_map(self, bl_index):
  164.         debug_print('Start build_id_playlist_map')
  165.         self.ensure_unique_playlist_titles()
  166.         self.prune_empty_playlists()
  167.         debug_print('after cleaning playlists')
  168.         root = self.record_roots[bl_index]
  169.         if root is None:
  170.             return None
  171.         id_map = self.build_id_map(root)
  172.         playlist_map = { }
  173.         for playlist in root.xpath('//*[local-name()="playlist"]'):
  174.             name = playlist.get('title')
  175.             if name is None:
  176.                 debug_print('build_id_playlist_map: unnamed playlist!')
  177.                 continue
  178.             
  179.             for item in playlist:
  180.                 id_ = item.get('id', None)
  181.                 if id_ is None:
  182.                     debug_print('build_id_playlist_map: id_ is None!')
  183.                     continue
  184.                 
  185.                 bk = id_map.get(id_, None)
  186.                 if bk is None:
  187.                     debug_print('build_id_playlist_map: book is None!', id_)
  188.                     continue
  189.                 
  190.                 lpath = bk.get('path', None)
  191.                 if lpath is None:
  192.                     debug_print('build_id_playlist_map: lpath is None!', id_)
  193.                     continue
  194.                 
  195.                 if lpath not in playlist_map:
  196.                     playlist_map[lpath] = []
  197.                 
  198.                 playlist_map[lpath].append(name)
  199.             
  200.         
  201.         debug_print('Finish build_id_playlist_map. Found', len(playlist_map))
  202.         return playlist_map
  203.  
  204.     
  205.     def reset_existing_playlists_map(self):
  206.         self._playlist_to_playlist_id_map = { }
  207.  
  208.     
  209.     def get_or_create_playlist(self, bl_idx, title):
  210.         root = self.record_roots[bl_idx]
  211.         if bl_idx not in self._playlist_to_playlist_id_map:
  212.             self._playlist_to_playlist_id_map[bl_idx] = { }
  213.             for playlist in root.xpath('//*[local-name()="playlist"]'):
  214.                 pl_title = playlist.get('title', None)
  215.                 if pl_title is not None:
  216.                     self._playlist_to_playlist_id_map[bl_idx][pl_title] = playlist
  217.                     continue
  218.             
  219.         
  220.         if title in self._playlist_to_playlist_id_map[bl_idx]:
  221.             return self._playlist_to_playlist_id_map[bl_idx][title]
  222.         debug_print('Creating playlist:', title)
  223.         ans = root.makeelement('{%s}playlist' % self.namespaces[bl_idx], nsmap = root.nsmap, attrib = {
  224.             'uuid': uuid(),
  225.             'title': title,
  226.             'id': str(self.max_id(root) + 1),
  227.             'sourceid': '1' })
  228.         root.append(ans)
  229.         self._playlist_to_playlist_id_map[bl_idx][title] = ans
  230.         return ans
  231.  
  232.     
  233.     def fix_ids(self):
  234.         debug_print('Running fix_ids()')
  235.         
  236.         def ensure_numeric_ids(root):
  237.             idmap = { }
  238.             for x in root.xpath('child::*[@id]'):
  239.                 id_ = x.get('id')
  240.                 
  241.                 try:
  242.                     id_ = int(id_)
  243.                 continue
  244.                 x.set('id', '-1')
  245.                 idmap[id_] = '-1'
  246.                 continue
  247.  
  248.             
  249.             if DEBUG and idmap:
  250.                 debug_print('Found non numeric ids:')
  251.                 debug_print(list(idmap.keys()))
  252.             
  253.             return idmap
  254.  
  255.         
  256.         def remap_playlist_references(root, idmap):
  257.             for playlist in root.xpath('//*[local-name()="playlist"]'):
  258.                 for item in playlist.xpath('descendant::*[@id and local-name()="item"]'):
  259.                     id_ = item.get('id')
  260.                     if id_ in idmap:
  261.                         item.set('id', idmap[id_])
  262.                         if DEBUG:
  263.                             debug_print('Remapping id %s to %s' % (id_, idmap[id_]))
  264.                         
  265.                     DEBUG
  266.                 
  267.             
  268.  
  269.         
  270.         def ensure_media_xml_base_ids(root):
  271.             for num, tag in enumerate(('library', 'watchSpecial')):
  272.                 for x in root.xpath('//*[local-name()="%s"]' % tag):
  273.                     x.set('id', str(num))
  274.                 
  275.             
  276.  
  277.         
  278.         def rebase_ids(root, base, sourceid, pl_sourceid):
  279.             for item in root.xpath('//*[@sourceid]'):
  280.                 sid = None if item.tag.endswith('playlist') else sourceid
  281.                 item.set('sourceid', str(sid))
  282.             
  283.             items = root.xpath('child::*[@id]')
  284.             items.sort(cmp = (lambda x, y: cmp(int(x.get('id')), int(y.get('id')))))
  285.             idmap = { }
  286.             for i, item in enumerate(items):
  287.                 old = int(item.get('id'))
  288.                 new = base + i
  289.                 if old != new:
  290.                     item.set('id', str(new))
  291.                     idmap[str(old)] = str(new)
  292.                     continue
  293.             
  294.             return idmap
  295.  
  296.         self.prune_empty_playlists()
  297.         for i in sorted(self.roots.keys()):
  298.             root = self.record_roots[i]
  299.             if i == 0:
  300.                 ensure_media_xml_base_ids(root)
  301.             
  302.             idmap = ensure_numeric_ids(root)
  303.             if len(idmap) > 0:
  304.                 debug_print('fix_ids: found some non-numeric ids')
  305.                 remap_playlist_references(root, idmap)
  306.             
  307.             if i == 0:
  308.                 (sourceid, playlist_sid) = (1, 0)
  309.                 base = 0
  310.             else:
  311.                 previous = i - 1
  312.                 if previous not in self.roots:
  313.                     previous = 0
  314.                 
  315.                 max_id = self.max_id(self.roots[previous])
  316.                 sourceid = playlist_sid = max_id + 1
  317.                 base = max_id + 2
  318.             idmap = rebase_ids(root, base, sourceid, playlist_sid)
  319.             remap_playlist_references(root, idmap)
  320.         
  321.         last_bl = max(self.roots.keys())
  322.         max_id = self.max_id(self.roots[last_bl])
  323.         self.roots[0].set('nextID', str(max_id + 1))
  324.         debug_print('Finished running fix_ids()')
  325.  
  326.     
  327.     def update_booklist(self, bl, bl_index):
  328.         if bl_index not in self.record_roots:
  329.             return None
  330.         debug_print('Updating JSON cache:', bl_index)
  331.         playlist_map = self.build_id_playlist_map(bl_index)
  332.         root = self.record_roots[bl_index]
  333.         lpath_map = self.build_lpath_map(root)
  334.         for book in bl:
  335.             record = lpath_map.get(book.lpath, None)
  336.             if record is not None:
  337.                 for thumbnail in record.xpath('descendant::*[local-name()="thumbnail"]'):
  338.                     for img in thumbnail.xpath('descendant::*[local-name()="jpeg"]|descendant::*[local-name()="png"]'):
  339.                         if img.text:
  340.                             
  341.                             try:
  342.                                 raw = b64decode(img.text.strip())
  343.                             except:
  344.                                 bl_index not in self.record_roots
  345.                                 continue
  346.  
  347.                             book.thumbnail = raw
  348.                             break
  349.                             continue
  350.                         bl_index not in self.record_roots
  351.                     
  352.                 
  353.                 book.device_collections = playlist_map.get(book.lpath, [])
  354.                 continue
  355.         
  356.         debug_print('Finished updating JSON cache:', bl_index)
  357.  
  358.     
  359.     def update(self, booklists, collections_attributes, plugboard):
  360.         debug_print('Starting update', collections_attributes)
  361.         use_tz_var = False
  362.         for i, booklist in booklists.items():
  363.             playlist_map = self.build_id_playlist_map(i)
  364.             debug_print('Updating XML Cache:', i)
  365.             root = self.record_roots[i]
  366.             lpath_map = self.build_lpath_map(root)
  367.             ext_root = None if i in self.ext_roots else None
  368.             ext_lpath_map = None
  369.             if ext_root is not None:
  370.                 ext_lpath_map = self.build_lpath_map(ext_root)
  371.             
  372.             gtz_count = ltz_count = 0
  373.             use_tz_var = False
  374.             for book in booklist:
  375.                 path = os.path.join(self.prefixes[i], *book.lpath.split('/'))
  376.                 record = lpath_map.get(book.lpath, None)
  377.                 created = False
  378.                 if record is None:
  379.                     created = True
  380.                     record = self.create_text_record(root, i, book.lpath)
  381.                 
  382.                 if plugboard is not None:
  383.                     newmi = book.deepcopy_metadata()
  384.                     newmi.template_to_attribute(book, plugboard)
  385.                     newmi.set('_new_book', getattr(book, '_new_book', False))
  386.                 else:
  387.                     newmi = book
  388.                 (gtz_count, ltz_count, use_tz_var) = self.update_text_record(record, newmi, path, i, gtz_count, ltz_count, use_tz_var)
  389.                 if book.device_collections is None:
  390.                     book.device_collections = []
  391.                 
  392.                 book.device_collections = playlist_map.get(book.lpath, [])
  393.                 if created and ext_root is not None and ext_lpath_map.get(book.lpath, None) is None:
  394.                     ext_record = self.create_ext_text_record(ext_root, i, book.lpath, book.thumbnail)
  395.                     self.periodicalize_book(book, ext_record)
  396.                     continue
  397.             
  398.             debug_print('Timezone votes: %d GMT, %d LTZ, use_tz_var=%s' % (gtz_count, ltz_count, use_tz_var))
  399.             self.update_playlists(i, root, booklist, collections_attributes)
  400.         
  401.         debug_print('In update/ Starting refresh of device_collections')
  402.         for i, booklist in booklists.items():
  403.             playlist_map = self.build_id_playlist_map(i)
  404.             for book in booklist:
  405.                 book.device_collections = playlist_map.get(book.lpath, [])
  406.             
  407.         
  408.         self.fix_ids()
  409.         debug_print('Finished update')
  410.  
  411.     
  412.     def is_sony_periodical(self, book):
  413.         if _('News') not in book.tags:
  414.             return False
  415.         if not book.lpath.lower().endswith('.epub'):
  416.             return False
  417.         if book.pubdate.date() < date(2010, 10, 17):
  418.             return False
  419.         return True
  420.  
  421.     
  422.     def periodicalize_book(self, book, record):
  423.         if not self.is_sony_periodical(book):
  424.             return None
  425.         record.set('conformsTo', 'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0')
  426.         record.set('description', '')
  427.         name = None
  428.         if '[' in book.title:
  429.             name = book.title.split('[')[0].strip()
  430.             if len(name) < 4:
  431.                 name = None
  432.             
  433.         
  434.         if not name:
  435.             
  436.             try:
  437.                 name = _[1][0]
  438.             name = None
  439.  
  440.         
  441.         if not name:
  442.             name = book.title
  443.         
  444.         record.set('periodicalName', name)
  445.         
  446.         try:
  447.             pubdate = strftime(book.pubdate.utctimetuple(), zone = (lambda x: x))
  448.             record.set('publicationDate', pubdate)
  449.         except:
  450.             pass
  451.  
  452.  
  453.     
  454.     def rebuild_collections(self, booklist, bl_index):
  455.         if bl_index not in self.record_roots:
  456.             return None
  457.         root = self.record_roots[bl_index]
  458.         self.update_playlists(bl_index, root, booklist, [])
  459.         self.fix_ids()
  460.  
  461.     
  462.     def update_playlists(self, bl_index, root, booklist, collections_attributes):
  463.         debug_print('Starting update_playlists', collections_attributes, bl_index)
  464.         self.reset_existing_playlists_map()
  465.         collections = booklist.get_collections(collections_attributes)
  466.         lpath_map = self.build_lpath_map(root)
  467.         debug_print('update_playlists: finished building maps')
  468.         for category, books in collections.items():
  469.             records = [ lpath_map.get(b.lpath, None) for b in books ]
  470.             records = _[2]
  471.             for rec in records:
  472.                 if rec.get('id', None) is None:
  473.                     rec.set('id', str(self.max_id(root) + 1))
  474.                     continue
  475.                 []
  476.             
  477.             ids = [ x.get('id', None) for x in records ]
  478.             playlist = self.get_or_create_playlist(bl_index, category)
  479.             playlist_ids = []
  480.             for item in playlist:
  481.                 id_ = item.get('id', None)
  482.                 if id_ is not None:
  483.                     playlist_ids.append(id_)
  484.                     continue
  485.                 [] if None in ids else []
  486.             
  487.             for item in list(playlist):
  488.                 playlist.remove(item)
  489.             
  490.             extra_ids = _[5]
  491.             for id_ in ids + extra_ids:
  492.                 item = playlist.makeelement('{%s}item' % self.namespaces[bl_index], nsmap = playlist.nsmap, attrib = {
  493.                     'id': id_ })
  494.                 playlist.append(item)
  495.             
  496.         
  497.         for playlist in root.xpath('//*[local-name()="playlist"]'):
  498.             title = playlist.get('title', None)
  499.             books = collections[title]
  500.             records = [ lpath_map.get(b.lpath, None) for b in books ]
  501.             records = _[7]
  502.             ids = [ x.get('id', None) for x in records ]
  503.             ids = _[9]
  504.             for item in list(playlist):
  505.                 if item.get('id', None) not in ids:
  506.                     playlist.remove(item)
  507.                     continue
  508.                 [] if DEBUG else []
  509.             
  510.         
  511.         debug_print('Finishing update_playlists')
  512.  
  513.     
  514.     def create_text_record(self, root, bl_id, lpath):
  515.         namespace = self.namespaces[bl_id]
  516.         id_ = self.max_id(root) + 1
  517.         attrib = {
  518.             'page': '0',
  519.             'part': '0',
  520.             'pageOffset': '0',
  521.             'scale': '0',
  522.             'id': str(id_),
  523.             'sourceid': '1',
  524.             'path': lpath }
  525.         ans = root.makeelement('{%s}text' % namespace, attrib = attrib, nsmap = root.nsmap)
  526.         root.append(ans)
  527.         return ans
  528.  
  529.     
  530.     def create_ext_text_record(self, root, bl_id, lpath, thumbnail):
  531.         namespace = root.nsmap[None]
  532.         attrib = {
  533.             'path': lpath }
  534.         ans = root.makeelement('{%s}text' % namespace, attrib = attrib, nsmap = root.nsmap)
  535.         ans.tail = '\n'
  536.         if len(root) > 0:
  537.             root[-1].tail = '\n\t'
  538.         else:
  539.             root.text = '\n\t'
  540.         root.append(ans)
  541.         if thumbnail and thumbnail[-1]:
  542.             ans.text = '\n\t\t'
  543.             t = root.makeelement('{%s}thumbnail' % namespace, attrib = {
  544.                 'width': str(thumbnail[0]),
  545.                 'height': str(thumbnail[1]) }, nsmap = root.nsmap)
  546.             t.text = 'main_thumbnail.jpg'
  547.             ans.append(t)
  548.             t.tail = '\n\t'
  549.         
  550.         return ans
  551.  
  552.     
  553.     def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var):
  554.         timestamp = os.path.getmtime(path)
  555.         rec_date = record.get('date', None)
  556.         
  557.         def clean(x):
  558.             if isbytestring(x):
  559.                 x = x.decode(preferred_encoding, 'replace')
  560.             
  561.             x.replace(u'\x00', '')
  562.             return x
  563.  
  564.         if not getattr(book, '_new_book', False):
  565.             if record.get('tz', None) is not None:
  566.                 use_tz_var = True
  567.             
  568.             if strftime(timestamp, zone = time.gmtime) == rec_date:
  569.                 gtz_count += 1
  570.             elif strftime(timestamp, zone = time.localtime) == rec_date:
  571.                 ltz_count += 1
  572.             
  573.         elif use_tz_var:
  574.             tz = time.localtime
  575.             record.set('tz', '0')
  576.             debug_print("Use localtime TZ and tz='0' for new book", book.lpath)
  577.         elif ltz_count >= gtz_count:
  578.             tz = time.localtime
  579.             debug_print('Use localtime TZ for new book', book.lpath)
  580.         else:
  581.             tz = time.gmtime
  582.             debug_print('Use GMT TZ for new book', book.lpath)
  583.         date = strftime(timestamp, zone = tz)
  584.         record.set('date', clean(date))
  585.         record.set('size', clean(str(os.stat(path).st_size)))
  586.         title = None if book.title else _('Unknown')
  587.         record.set('title', clean(title))
  588.         ts = book.title_sort
  589.         if not ts:
  590.             ts = title_sort(title)
  591.         
  592.         record.set('titleSorter', clean(ts))
  593.         if self.use_author_sort:
  594.             if book.author_sort:
  595.                 aus = book.author_sort
  596.             else:
  597.                 debug_print('Author_sort is None for book', book.lpath)
  598.                 aus = authors_to_sort_string(book.authors)
  599.             record.set('author', clean(aus))
  600.         else:
  601.             record.set('author', clean(authors_to_string(book.authors)))
  602.         ext = os.path.splitext(path)[1]
  603.         if ext:
  604.             ext = ext[1:].lower()
  605.             mime = MIME_MAP.get(ext, None)
  606.             if mime is None:
  607.                 mime = guess_type('a.' + ext)[0]
  608.             
  609.             if mime is not None:
  610.                 record.set('mime', clean(mime))
  611.             
  612.         
  613.         if 'sourceid' not in record.attrib:
  614.             record.set('sourceid', '1')
  615.         
  616.         if 'id' not in record.attrib:
  617.             num = self.max_id(record.getroottree().getroot())
  618.             record.set('id', str(num + 1))
  619.         
  620.         return (gtz_count, ltz_count, use_tz_var)
  621.  
  622.     
  623.     def cleanup_whitespace(self, bl_index):
  624.         root = self.record_roots[bl_index]
  625.         level = None if bl_index == 0 else 1
  626.         if len(root) > 0:
  627.             root.text = '\n' + '\t' * level
  628.             for child in root:
  629.                 child.tail = '\n' + '\t' * level
  630.                 if len(child) > 0:
  631.                     child.text = '\n' + '\t' * (level + 1)
  632.                     for gc in child:
  633.                         gc.tail = '\n' + '\t' * (level + 1)
  634.                     
  635.                     child.iterchildren(reversed = True).next().tail = '\n' + '\t' * level
  636.                     continue
  637.             
  638.             root.iterchildren(reversed = True).next().tail = '\n' + '\t' * (level - 1)
  639.         
  640.  
  641.     
  642.     def move_playlists_to_bottom(self):
  643.         for root in self.record_roots.values():
  644.             seen = []
  645.             for pl in root.xpath('//*[local-name()="playlist"]'):
  646.                 pl.getparent().remove(pl)
  647.                 seen.append(pl)
  648.             
  649.             for pl in seen:
  650.                 root.append(pl)
  651.             
  652.         
  653.  
  654.     
  655.     def write(self):
  656.         for i, path in self.paths.items():
  657.             self.move_playlists_to_bottom()
  658.             self.cleanup_whitespace(i)
  659.             raw = etree.tostring(self.roots[i], encoding = 'UTF-8', xml_declaration = True)
  660.             raw = raw.replace("<?xml version='1.0' encoding='UTF-8'?>", '<?xml version="1.0" encoding="UTF-8"?>')
  661.             
  662.             try:
  663.                 f = _[1]
  664.                 f.write(raw)
  665.             finally:
  666.                 pass
  667.  
  668.         
  669.         for i, path in self.ext_paths.items():
  670.             
  671.             try:
  672.                 raw = etree.tostring(self.ext_roots[i], encoding = 'UTF-8', xml_declaration = True)
  673.             except:
  674.                 open(path, 'wb').__exit__
  675.                 open(path, 'wb')
  676.                 continue
  677.  
  678.             raw = raw.replace("<?xml version='1.0' encoding='UTF-8'?>", '<?xml version="1.0" encoding="UTF-8"?>')
  679.             
  680.             try:
  681.                 f = _[2]
  682.                 f.write(raw)
  683.             finally:
  684.                 pass
  685.  
  686.         
  687.  
  688.     
  689.     def build_lpath_map(self, root):
  690.         m = { }
  691.         for bk in root.xpath('//*[local-name()="text"]'):
  692.             m[bk.get('path')] = bk
  693.         
  694.         return m
  695.  
  696.     
  697.     def build_id_map(self, root):
  698.         m = { }
  699.         for bk in root.xpath('//*[local-name()="text"]'):
  700.             m[bk.get('id')] = bk
  701.         
  702.         return m
  703.  
  704.     
  705.     def book_by_lpath(self, lpath, root):
  706.         matches = root.xpath(u'//*[local-name()="text" and @path="%s"]' % lpath)
  707.         if matches:
  708.             return matches[0]
  709.  
  710.     
  711.     def max_id(self, root):
  712.         ans = -1
  713.         for x in root.xpath('//*[@id]'):
  714.             id_ = x.get('id')
  715.             
  716.             try:
  717.                 num = int(id_)
  718.                 if num > ans:
  719.                     ans = num
  720.             continue
  721.             continue
  722.             continue
  723.  
  724.         
  725.         return ans
  726.  
  727.     
  728.     def detect_namespaces(self):
  729.         self.nsmaps = { }
  730.         for i, root in self.roots.items():
  731.             self.nsmaps[i] = root.nsmap
  732.         
  733.         self.namespaces = { }
  734.         for i in self.roots:
  735.             for c in ('library', 'text', 'image', 'playlist', 'thumbnail', 'watchSpecial'):
  736.                 matches = self.record_roots[i].xpath('//*[local-name()="%s"]' % c)
  737.                 if matches:
  738.                     e = matches[0]
  739.                     self.namespaces[i] = e.nsmap[e.prefix]
  740.                     break
  741.                     continue
  742.             
  743.             if i not in self.namespaces:
  744.                 ns = self.nsmaps[i].get(None, None)
  745.                 for prefix in self.nsmaps[i]:
  746.                     if prefix is not None:
  747.                         ns = self.nsmaps[i][prefix]
  748.                         break
  749.                         continue
  750.                 
  751.                 self.namespaces[i] = ns
  752.                 continue
  753.         
  754.  
  755.  
  756.