home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_909 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  26.1 KB  |  825 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__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
  7. from cStringIO import StringIO
  8. from struct import pack
  9. from itertools import izip, count, chain
  10. import time
  11. import random
  12. import re
  13. import copy
  14. import uuid
  15. import functools
  16. from urlparse import urldefrag
  17. from urllib import unquote as urlunquote
  18. from lxml import etree
  19. from calibre.ebooks.lit.reader import DirectoryEntry
  20. import calibre.ebooks.lit.maps as maps
  21. from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, OEB_STYLES, CSS_MIME, OPF_MIME, XML_NS, XML
  22. from calibre.ebooks.oeb.base import prefixname, urlnormalize
  23. from calibre.ebooks.oeb.stylizer import Stylizer
  24. from calibre.ebooks.lit.lzx import Compressor
  25. import calibre
  26. from calibre import plugins
  27. (msdes, msdeserror) = plugins['msdes']
  28. import calibre.ebooks.lit.mssha1 as mssha1
  29. __all__ = [
  30.     'LitWriter']
  31. LIT_IMAGES = set([
  32.     'image/png',
  33.     'image/jpeg',
  34.     'image/gif'])
  35. LIT_MIMES = OEB_DOCS | OEB_STYLES | LIT_IMAGES
  36. MS_COVER_TYPE = 'other.ms-coverimage-standard'
  37. ALL_MS_COVER_TYPES = [
  38.     (MS_COVER_TYPE, 'Standard cover image'),
  39.     ('other.ms-thumbimage-standard', 'Standard thumbnail image'),
  40.     ('other.ms-coverimage', 'PocketPC cover image'),
  41.     ('other.ms-thumbimage', 'PocketPC thumbnail image')]
  42.  
  43. def invert_tag_map(tag_map):
  44.     (tags, dattrs, tattrs) = tag_map
  45.     tags = (dict,)((lambda .0: for i in .0:
  46. (tags[i], i))(xrange(len(tags))))
  47.     dattrs = dict((lambda .0: for k, v in .0:
  48. (v, k))(dattrs.items()))
  49.     tattrs = [ dict((lambda .0: for k, v in .0:
  50. (v, k))({ }.items())) for map in tattrs ]
  51.     for map in tattrs:
  52.         if map:
  53.             map.update(dattrs)
  54.             continue
  55.         []
  56.     
  57.     tattrs[0] = dattrs
  58.     return (tags, tattrs)
  59.  
  60. OPF_MAP = invert_tag_map(maps.OPF_MAP)
  61. HTML_MAP = invert_tag_map(maps.HTML_MAP)
  62. LIT_MAGIC = 'ITOLITLS'
  63. LITFILE_GUID = '{0A9007C1-4076-11D3-8789-0000F8105754}'
  64. PIECE3_GUID = '{0A9007C3-4076-11D3-8789-0000F8105754}'
  65. PIECE4_GUID = '{0A9007C4-4076-11D3-8789-0000F8105754}'
  66. DESENCRYPT_GUID = '{67F6E4A2-60BF-11D3-8540-00C04F58C3CF}'
  67. LZXCOMPRESS_GUID = '{0A9007C6-4076-11D3-8789-0000F8105754}'
  68.  
  69. def packguid(guid):
  70.     values = (guid[1:9], guid[10:14], guid[15:19], guid[20:22], guid[22:24], guid[25:27], guid[27:29], guid[29:31], guid[31:33], guid[33:35], guid[35:37])
  71.     values = [ int(value, 16) for value in values ]
  72.     return pack('<LHHBBBBBBBB', *values)
  73.  
  74. FLAG_OPENING = 1
  75. FLAG_CLOSING = 2
  76. FLAG_BLOCK = 4
  77. FLAG_HEAD = 8
  78. FLAG_ATOM = 16
  79. FLAG_CUSTOM = 32768
  80. ATTR_NUMBER = 65535
  81. PIECE_SIZE = 16
  82. PRIMARY_SIZE = 40
  83. SECONDARY_SIZE = 232
  84. DCHUNK_SIZE = 8192
  85. CCHUNK_SIZE = 512
  86. ULL_NEG1 = 0xFFFFFFFFFFFFFFFFL
  87. ROOT_OFFSET = 0x11D37DC0CA67E278L
  88. ROOT_SIZE = 0x39D0724FC0006593L
  89. BLOCK_CAOL = 'CAOL\x02\x00\x00\x00P\x00\x00\x007\x13\x03\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x02\x00\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
  90. BLOCK_ITSF = 'ITSF\x04\x00\x00\x00 \x00\x00\x00\x01\x00\x00\x00'
  91. MSDES_CONTROL = '\x03\x00\x00\x00)\x17\x00\x00\x01\x00\x00\x00\xa5\xa5\x00\x00'
  92. LZXC_CONTROL = '\x07\x00\x00\x00LZXC\x03\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
  93. COLLAPSE = re.compile('[ \\t\\r\\n\\v]+')
  94. PAGE_BREAKS = set([
  95.     'always',
  96.     'left',
  97.     'right'])
  98.  
  99. def decint(value):
  100.     bytes = []
  101.     while True:
  102.         b = value & 127
  103.         value >>= 7
  104.         if bytes:
  105.             b |= 128
  106.         
  107.         bytes.append(chr(b))
  108.         if value == 0:
  109.             break
  110.             continue
  111.     return ''.join(reversed(bytes))
  112.  
  113.  
  114. def randbytes(n):
  115.     return ''.join((lambda .0: for x in .0:
  116. chr(random.randint(0, 255)))(xrange(n)))
  117.  
  118.  
  119. def warn(x):
  120.     print x
  121.  
  122.  
  123. class ReBinary(object):
  124.     NSRMAP = {
  125.         '': None,
  126.         XML_NS: 'xml' }
  127.     
  128.     def __init__(self, root, item, oeb, opts, map = HTML_MAP):
  129.         self.item = item
  130.         self.logger = oeb.logger
  131.         self.manifest = oeb.manifest
  132.         (self.tags, self.tattrs) = map
  133.         self.buf = StringIO()
  134.         self.anchors = []
  135.         self.page_breaks = []
  136.         self.is_html = is_html = map is HTML_MAP
  137.         self.stylizer = None if is_html else None
  138.         self.tree_to_binary(root)
  139.         self.content = self.buf.getvalue()
  140.         self.ahc = None if is_html else None
  141.         self.aht = None if is_html else None
  142.  
  143.     
  144.     def write(self, *values):
  145.         for value in values:
  146.             if isinstance(value, (int, long)):
  147.                 
  148.                 try:
  149.                     value = unichr(value)
  150.                 except OverflowError:
  151.                     self.logger.warn('Unicode overflow for integer:', value)
  152.                     value = u'?'
  153.                 except:
  154.                     None<EXCEPTION MATCH>OverflowError
  155.                 
  156.  
  157.             None<EXCEPTION MATCH>OverflowError
  158.             self.buf.write(value.encode('utf-8'))
  159.         
  160.  
  161.     
  162.     def is_block(self, style):
  163.         return style['display'] not in ('inline', 'inline-block')
  164.  
  165.     
  166.     def tree_to_binary(self, elem, nsrmap = NSRMAP, parents = [], inhead = False, preserve = False):
  167.         if not isinstance(elem.tag, basestring):
  168.             return None
  169.         nsrmap = copy.copy(nsrmap)
  170.         attrib = dict(elem.attrib)
  171.         style = isinstance(elem.tag, basestring) if self.stylizer else None
  172.         for key, value in elem.nsmap.items():
  173.             if value not in nsrmap or nsrmap[value] != key:
  174.                 xmlns = None if key else 'xmlns'
  175.                 attrib[xmlns] = value
  176.             
  177.             nsrmap[value] = key
  178.         
  179.         tag = prefixname(elem.tag, nsrmap)
  180.         tag_offset = self.buf.tell()
  181.         if tag == 'head':
  182.             inhead = True
  183.         
  184.         flags = FLAG_OPENING
  185.         if not (elem.text) and len(elem) == 0:
  186.             flags |= FLAG_CLOSING
  187.         
  188.         if inhead:
  189.             flags |= FLAG_HEAD
  190.         
  191.         if style and self.is_block(style):
  192.             flags |= FLAG_BLOCK
  193.         
  194.         self.write(0, flags)
  195.         tattrs = self.tattrs[0]
  196.         if tag in self.tags:
  197.             index = self.tags[tag]
  198.             self.write(index)
  199.             if self.tattrs[index]:
  200.                 tattrs = self.tattrs[index]
  201.             
  202.         else:
  203.             self.write(FLAG_CUSTOM, len(tag) + 1, tag)
  204.         last_break = None if self.page_breaks else None
  205.         if style and last_break != tag_offset and style['page-break-before'] in PAGE_BREAKS:
  206.             self.page_breaks.append((tag_offset, list(parents)))
  207.         
  208.         for attr, value in attrib.items():
  209.             attr = prefixname(attr, nsrmap)
  210.             if attr in ('href', 'src'):
  211.                 value = urlnormalize(value)
  212.                 (path, frag) = urldefrag(value)
  213.                 if self.item:
  214.                     path = self.item.abshref(path)
  215.                 
  216.                 prefix = unichr(3)
  217.                 if path in self.manifest.hrefs:
  218.                     prefix = unichr(2)
  219.                     value = self.manifest.hrefs[path].id
  220.                     if frag:
  221.                         value = '#'.join((value, frag))
  222.                     
  223.                 
  224.                 value = prefix + value
  225.             elif attr in ('id', 'name'):
  226.                 self.anchors.append((value, tag_offset))
  227.             elif attr.startswith('ms--'):
  228.                 attr = '%' + attr[4:]
  229.             elif tag == 'link' and attr == 'type' and value in OEB_STYLES:
  230.                 value = CSS_MIME
  231.             
  232.             if attr in tattrs:
  233.                 self.write(tattrs[attr])
  234.             else:
  235.                 self.write(FLAG_CUSTOM, len(attr) + 1, attr)
  236.             
  237.             try:
  238.                 self.write(ATTR_NUMBER, int(value) + 1)
  239.             continue
  240.             except ValueError:
  241.                 self.write(len(value) + 1, value)
  242.                 continue
  243.             
  244.  
  245.         
  246.         self.write(0)
  247.         old_preserve = preserve
  248.         if style:
  249.             preserve = style['white-space'] in ('pre', 'pre-wrap')
  250.         
  251.         xml_space = elem.get(XML('space'))
  252.         if xml_space == 'preserve':
  253.             preserve = True
  254.         elif xml_space == 'normal':
  255.             preserve = False
  256.         
  257.         if elem.text:
  258.             if preserve:
  259.                 self.write(elem.text)
  260.             elif len(elem) == 0 or not elem.text.isspace():
  261.                 self.write(COLLAPSE.sub(' ', elem.text))
  262.             
  263.         
  264.         parents.append(tag_offset)
  265.         child = None
  266.         cstyle = None
  267.         nstyle = None
  268.         for next in chain(elem, [
  269.             None]):
  270.             if self.stylizer:
  271.                 nstyle = None if next is None else self.stylizer.style(next)
  272.             
  273.             if child is not None:
  274.                 if not preserve:
  275.                     if (inhead and not nstyle and self.is_block(cstyle) or self.is_block(nstyle)) and child.tail and child.tail.isspace():
  276.                         child.tail = None
  277.                     
  278.                 self.tree_to_binary(child, nsrmap, parents, inhead, preserve)
  279.             
  280.             child = next
  281.             cstyle = nstyle
  282.         
  283.         parents.pop()
  284.         preserve = old_preserve
  285.         if not flags & FLAG_CLOSING:
  286.             self.write(0, flags & ~FLAG_OPENING | FLAG_CLOSING, 0)
  287.         
  288.         if elem.tail and tag != 'html':
  289.             tail = elem.tail
  290.             if not preserve:
  291.                 tail = COLLAPSE.sub(' ', tail)
  292.             
  293.             self.write(tail)
  294.         
  295.         if style and style['page-break-after'] not in ('avoid', 'auto'):
  296.             self.page_breaks.append((self.buf.tell(), list(parents)))
  297.         
  298.  
  299.     
  300.     def build_ahc(self):
  301.         if len(self.anchors) > 6:
  302.             self.logger.warn('More than six anchors in file %r. Some links may not work properly.' % self.item.href)
  303.         
  304.         data = StringIO()
  305.         data.write(unichr(len(self.anchors)).encode('utf-8'))
  306.         for anchor, offset in self.anchors:
  307.             data.write(unichr(len(anchor)).encode('utf-8'))
  308.             data.write(anchor)
  309.             data.write(pack('<I', offset))
  310.         
  311.         return data.getvalue()
  312.  
  313.     
  314.     def build_aht(self):
  315.         return pack('<I', 0)
  316.  
  317.  
  318.  
  319. def preserve(function):
  320.     
  321.     def wrapper(self, *args, **kwargs):
  322.         opos = self._stream.tell()
  323.         
  324.         try:
  325.             return function(self, *args, **kwargs)
  326.         finally:
  327.             self._stream.seek(opos)
  328.  
  329.  
  330.     functools.update_wrapper(wrapper, function)
  331.     return wrapper
  332.  
  333.  
  334. class LitWriter(object):
  335.     
  336.     def __init__(self, opts):
  337.         self.opts = opts
  338.  
  339.     
  340.     def _litize_oeb(self):
  341.         oeb = self._oeb
  342.         oeb.metadata.add('calibre-version', calibre.__version__)
  343.         cover = None
  344.         if oeb.metadata.cover:
  345.             id = unicode(oeb.metadata.cover[0])
  346.             cover = oeb.manifest.ids[id]
  347.             for type, title in ALL_MS_COVER_TYPES:
  348.                 if type not in oeb.guide:
  349.                     oeb.guide.add(type, title, cover.href)
  350.                     continue
  351.             
  352.         else:
  353.             self._logger.warn('No suitable cover image found.')
  354.  
  355.     
  356.     def __call__(self, oeb, path):
  357.         if hasattr(path, 'write'):
  358.             return self._dump_stream(oeb, path)
  359.         
  360.         try:
  361.             stream = _[1]
  362.             return self._dump_stream(oeb, stream)
  363.         finally:
  364.             pass
  365.  
  366.  
  367.     
  368.     def _dump_stream(self, oeb, stream):
  369.         self._oeb = oeb
  370.         self._logger = oeb.logger
  371.         self._stream = stream
  372.         self._sections = [ StringIO() for i in xrange(4) ]
  373.         self._directory = []
  374.         self._meta = None
  375.         self._litize_oeb()
  376.         self._write_content()
  377.  
  378.     
  379.     def _write(self, *data):
  380.         for datum in data:
  381.             self._stream.write(datum)
  382.         
  383.  
  384.     
  385.     def _writeat(self, pos, *data):
  386.         self._stream.seek(pos)
  387.         self._write(*data)
  388.  
  389.     _writeat = preserve(_writeat)
  390.     
  391.     def _tell(self):
  392.         return self._stream.tell()
  393.  
  394.     
  395.     def _write_content(self):
  396.         self._build_sections()
  397.         (dcounts, dchunks, ichunk) = self._build_dchunks()
  398.         self._write(LIT_MAGIC)
  399.         self._write(pack('<IIII', 1, PRIMARY_SIZE, 5, SECONDARY_SIZE))
  400.         self._write(packguid(LITFILE_GUID))
  401.         offset = self._tell()
  402.         pieces = list(xrange(offset, offset + PIECE_SIZE * 5, PIECE_SIZE))
  403.         self._write(5 * PIECE_SIZE * '\x00')
  404.         aoli1 = None if ichunk else ULL_NEG1
  405.         last = len(dchunks) - 1
  406.         ddepth = None if ichunk else 1
  407.         self._write(pack('<IIQQQQIIIIQIIQQQQIIIIQIIIIQ', 2, 152, aoli1, 0, last, 0, DCHUNK_SIZE, 2, 0, ddepth, 0, len(self._directory), 0, ULL_NEG1, 0, 0, 0, CCHUNK_SIZE, 2, 0, 1, 0, len(dcounts), 0, 1048576, 131072, 0))
  408.         self._write(BLOCK_CAOL)
  409.         self._write(BLOCK_ITSF)
  410.         conoff_offset = self._tell()
  411.         timestamp = int(time.time())
  412.         self._write(pack('<QII', 0, timestamp, 1033))
  413.         piece0_offset = self._tell()
  414.         self._write(pack('<II', 510, 0))
  415.         filesz_offset = self._tell()
  416.         self._write(pack('<QQ', 0, 0))
  417.         self._writeat(pieces[0], pack('<QQ', piece0_offset, self._tell() - piece0_offset))
  418.         piece1_offset = self._tell()
  419.         if not ichunk or 1:
  420.             pass
  421.         number = len(dchunks) + 0
  422.         self._write('IFCM', pack('<IIIQQ', 1, DCHUNK_SIZE, 1048576, ULL_NEG1, number))
  423.         for dchunk in dchunks:
  424.             self._write(dchunk)
  425.         
  426.         if ichunk:
  427.             self._write(ichunk)
  428.         
  429.         self._writeat(pieces[1], pack('<QQ', piece1_offset, self._tell() - piece1_offset))
  430.         piece2_offset = self._tell()
  431.         self._write('IFCM', pack('<IIIQQ', 1, CCHUNK_SIZE, 131072, ULL_NEG1, 1))
  432.         cchunk = StringIO()
  433.         last = 0
  434.         for i, dcount in izip(count(), dcounts):
  435.             cchunk.write(decint(last))
  436.             cchunk.write(decint(dcount))
  437.             cchunk.write(decint(i))
  438.             last = dcount
  439.         
  440.         cchunk = cchunk.getvalue()
  441.         rem = CCHUNK_SIZE - (len(cchunk) + 50)
  442.         self._write('AOLL', pack('<IQQQQQ', rem, 0, ULL_NEG1, ULL_NEG1, 0, 1))
  443.         filler = '\x00' * rem
  444.         self._write(cchunk, filler, pack('<H', len(dcounts)))
  445.         self._writeat(pieces[2], pack('<QQ', piece2_offset, self._tell() - piece2_offset))
  446.         piece3_offset = self._tell()
  447.         self._write(packguid(PIECE3_GUID))
  448.         self._writeat(pieces[3], pack('<QQ', piece3_offset, self._tell() - piece3_offset))
  449.         piece4_offset = self._tell()
  450.         self._write(packguid(PIECE4_GUID))
  451.         self._writeat(pieces[4], pack('<QQ', piece4_offset, self._tell() - piece4_offset))
  452.         content_offset = self._tell()
  453.         self._writeat(conoff_offset, pack('<Q', content_offset))
  454.         self._write(self._sections[0].getvalue())
  455.         self._writeat(filesz_offset, pack('<Q', self._tell()))
  456.  
  457.     
  458.     def _add_file(self, name, data, secnum = 0):
  459.         if len(data) > 0:
  460.             section = self._sections[secnum]
  461.             offset = section.tell()
  462.             section.write(data)
  463.         else:
  464.             offset = 0
  465.         self._directory.append(DirectoryEntry(name, secnum, offset, len(data)))
  466.  
  467.     
  468.     def _add_folder(self, name, offset = 0, size = 0):
  469.         if not name.endswith('/'):
  470.             name += '/'
  471.         
  472.         self._directory.append(DirectoryEntry(name, 0, offset, size))
  473.  
  474.     
  475.     def _djoin(self, *names):
  476.         return '/'.join(names)
  477.  
  478.     
  479.     def _build_sections(self):
  480.         self._add_folder('/', ROOT_OFFSET, ROOT_SIZE)
  481.         self._build_data()
  482.         self._build_manifest()
  483.         self._build_page_breaks()
  484.         self._build_meta()
  485.         self._build_drm_storage()
  486.         self._build_version()
  487.         self._build_namelist()
  488.         self._build_storage()
  489.         self._build_transforms()
  490.  
  491.     
  492.     def _build_data(self):
  493.         self._add_folder('/data')
  494.         for item in self._oeb.manifest.values():
  495.             if item.media_type not in LIT_MIMES:
  496.                 self._logger.warn('File %r of unknown media-type %r excluded from output.' % (item.href, item.media_type))
  497.                 continue
  498.             
  499.             name = '/data/' + item.id
  500.             data = item.data
  501.             secnum = 0
  502.             if isinstance(data, etree._Element):
  503.                 self._add_folder(name)
  504.                 rebin = ReBinary(data, item, self._oeb, self.opts, map = HTML_MAP)
  505.                 self._add_file(name + '/ahc', rebin.ahc, 0)
  506.                 self._add_file(name + '/aht', rebin.aht, 0)
  507.                 item.page_breaks = rebin.page_breaks
  508.                 data = rebin.content
  509.                 name = name + '/content'
  510.                 secnum = 1
  511.             elif isinstance(data, unicode):
  512.                 data = data.encode('utf-8')
  513.             elif hasattr(data, 'cssText'):
  514.                 data = str(item)
  515.             
  516.             self._add_file(name, data, secnum)
  517.             item.size = len(data)
  518.         
  519.  
  520.     
  521.     def _build_manifest(self):
  522.         states = [
  523.             'linear',
  524.             'nonlinear',
  525.             'css',
  526.             'images']
  527.         manifest = dict((lambda .0: for state in .0:
  528. (state, []))(states))
  529.         for item in self._oeb.manifest.values():
  530.             if item.spine_position is not None:
  531.                 key = None if item.linear else 'nonlinear'
  532.                 manifest[key].append(item)
  533.                 continue
  534.             if item.media_type in OEB_STYLES:
  535.                 manifest['css'].append(item)
  536.                 continue
  537.             if item.media_type in LIT_IMAGES:
  538.                 manifest['images'].append(item)
  539.                 continue
  540.         
  541.         data = StringIO()
  542.         data.write(pack('<Bc', 1, '\\'))
  543.         offset = 0
  544.         for state in states:
  545.             items = manifest[state]
  546.             items.sort()
  547.             data.write(pack('<I', len(items)))
  548.             for item in items:
  549.                 id = item.id
  550.                 media_type = item.media_type
  551.                 if media_type in OEB_DOCS:
  552.                     media_type = XHTML_MIME
  553.                 elif media_type in OEB_STYLES:
  554.                     media_type = CSS_MIME
  555.                 
  556.                 href = urlunquote(item.href)
  557.                 item.offset = None if state in ('linear', 'nonlinear') else 0
  558.                 data.write(pack('<I', item.offset))
  559.                 entry = [
  560.                     unichr(len(id)),
  561.                     unicode(id),
  562.                     unichr(len(href)),
  563.                     unicode(href),
  564.                     unichr(len(media_type)),
  565.                     unicode(media_type)]
  566.                 for value in entry:
  567.                     data.write(value.encode('utf-8'))
  568.                 
  569.                 data.write('\x00')
  570.                 offset += item.size
  571.             
  572.         
  573.         self._add_file('/manifest', data.getvalue())
  574.  
  575.     
  576.     def _build_page_breaks(self):
  577.         pb1 = StringIO()
  578.         pb2 = StringIO()
  579.         pb3 = StringIO()
  580.         pb3cur = 0
  581.         bits = 0
  582.         linear = []
  583.         nonlinear = []
  584.         for item in self._oeb.spine:
  585.             dest = None if item.linear else nonlinear
  586.             dest.append(item)
  587.         
  588.         for item in chain(linear, nonlinear):
  589.             page_breaks = copy.copy(item.page_breaks)
  590.             if not item.linear:
  591.                 page_breaks.insert(0, (0, []))
  592.             
  593.             for pbreak, parents in page_breaks:
  594.                 pb3cur = pb3cur << 2 | 1
  595.                 if len(parents) > 1:
  596.                     pb3cur |= 2
  597.                 
  598.                 bits += 2
  599.                 if bits >= 8:
  600.                     pb3.write(pack('<B', pb3cur))
  601.                     pb3cur = 0
  602.                     bits = 0
  603.                 
  604.                 pbreak += item.offset
  605.                 pb1.write(pack('<II', pbreak, pb2.tell()))
  606.                 pb2.write(pack('<I', len(parents)))
  607.                 for parent in parents:
  608.                     pb2.write(pack('<I', parent))
  609.                 
  610.             
  611.         
  612.         if bits != 0:
  613.             pb3cur <<= 8 - bits
  614.             pb3.write(pack('<B', pb3cur))
  615.         
  616.         self._add_file('/pb1', pb1.getvalue(), 0)
  617.         self._add_file('/pb2', pb2.getvalue(), 0)
  618.         self._add_file('/pb3', pb3.getvalue(), 0)
  619.  
  620.     
  621.     def _build_meta(self):
  622.         (_, meta) = self._oeb.to_opf1()[OPF_MIME]
  623.         meta.attrib['ms--minimum_level'] = '0'
  624.         meta.attrib['ms--attr5'] = '1'
  625.         meta.attrib['ms--guid'] = '{%s}' % str(uuid.uuid4()).upper()
  626.         rebin = ReBinary(meta, None, self._oeb, self.opts, map = OPF_MAP)
  627.         meta = rebin.content
  628.         self._meta = meta
  629.         self._add_file('/meta', meta)
  630.  
  631.     
  632.     def _build_drm_storage(self):
  633.         drmsource = u'Free as in freedom\x00'.encode('utf-16-le')
  634.         self._add_file('/DRMStorage/DRMSource', drmsource)
  635.         tempkey = self._calculate_deskey([
  636.             self._meta,
  637.             drmsource])
  638.         msdes.deskey(tempkey, msdes.EN0)
  639.         self._add_file('/DRMStorage/DRMSealed', msdes.des('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
  640.         self._bookkey = '\x00\x00\x00\x00\x00\x00\x00\x00'
  641.         self._add_file('/DRMStorage/ValidationStream', 'MSReader', 3)
  642.  
  643.     
  644.     def _build_version(self):
  645.         self._add_file('/Version', pack('<HH', 8, 1))
  646.  
  647.     
  648.     def _build_namelist(self):
  649.         data = StringIO()
  650.         data.write(pack('<HH', 60, len(self._sections)))
  651.         names = [
  652.             'Uncompressed',
  653.             'MSCompressed',
  654.             'EbEncryptDS',
  655.             'EbEncryptOnlyDS']
  656.         for name in names:
  657.             data.write(pack('<H', len(name)))
  658.             data.write(name.encode('utf-16-le'))
  659.             data.write('\x00\x00')
  660.         
  661.         self._add_file('::DataSpace/NameList', data.getvalue())
  662.  
  663.     
  664.     def _build_storage(self):
  665.         mapping = [
  666.             (1, 'MSCompressed', (LZXCOMPRESS_GUID,)),
  667.             (2, 'EbEncryptDS', (LZXCOMPRESS_GUID, DESENCRYPT_GUID)),
  668.             (3, 'EbEncryptOnlyDS', (DESENCRYPT_GUID,))]
  669.         for secnum, name, transforms in mapping:
  670.             root = '::DataSpace/Storage/' + name
  671.             data = self._sections[secnum].getvalue()
  672.             (cdata, sdata, tdata, rdata) = ('', '', '', '')
  673.             for guid in transforms:
  674.                 tdata = packguid(guid) + tdata
  675.                 sdata = sdata + pack('<Q', len(data))
  676.                 if guid == DESENCRYPT_GUID:
  677.                     cdata = MSDES_CONTROL + cdata
  678.                     if not data:
  679.                         continue
  680.                     
  681.                     msdes.deskey(self._bookkey, msdes.EN0)
  682.                     pad = 8 - (len(data) & 7)
  683.                     if pad != 8:
  684.                         data = data + '\x00' * pad
  685.                     
  686.                     data = msdes.des(data)
  687.                     continue
  688.                 if guid == LZXCOMPRESS_GUID:
  689.                     cdata = LZXC_CONTROL + cdata
  690.                     if not data:
  691.                         continue
  692.                     
  693.                     unlen = len(data)
  694.                     lzx = Compressor(17)
  695.                     (data, rtable) = lzx.compress(data, flush = True)
  696.                     rdata = StringIO()
  697.                     rdata.write(pack('<IIIIQQQQ', 3, len(rtable), 8, 40, unlen, len(data), 32768, 0))
  698.                     for uncomp, comp in rtable[:-1]:
  699.                         rdata.write(pack('<Q', comp))
  700.                     
  701.                     rdata = rdata.getvalue()
  702.                     continue
  703.             
  704.             self._add_file(root + '/Content', data)
  705.             self._add_file(root + '/ControlData', cdata)
  706.             self._add_file(root + '/SpanInfo', sdata)
  707.             self._add_file(root + '/Transform/List', tdata)
  708.             troot = root + '/Transform'
  709.             for guid in transforms:
  710.                 dname = self._djoin(troot, guid, 'InstanceData')
  711.                 self._add_folder(dname)
  712.                 if guid == LZXCOMPRESS_GUID:
  713.                     dname += '/ResetTable'
  714.                     self._add_file(dname, rdata)
  715.                     continue
  716.             
  717.         
  718.  
  719.     
  720.     def _build_transforms(self):
  721.         for guid in (LZXCOMPRESS_GUID, DESENCRYPT_GUID):
  722.             self._add_folder('::Transform/' + guid)
  723.         
  724.  
  725.     
  726.     def _calculate_deskey(self, hashdata):
  727.         prepad = 2
  728.         hash = mssha1.new()
  729.         for data in hashdata:
  730.             if prepad > 0:
  731.                 data = '\x00' * prepad + data
  732.                 prepad = 0
  733.             
  734.             postpad = 64 - len(data) % 64
  735.             if postpad < 64:
  736.                 data = data + '\x00' * postpad
  737.             
  738.             hash.update(data)
  739.         
  740.         digest = hash.digest()
  741.         key = [
  742.             0] * 8
  743.         for i in xrange(0, len(digest)):
  744.             key[i % 8] ^= ord(digest[i])
  745.         
  746.         return ''.join((lambda .0: for x in .0:
  747. chr(x))(key))
  748.  
  749.     
  750.     def _build_dchunks(self):
  751.         ddata = []
  752.         directory = list(self._directory)
  753.         directory.sort(cmp = (lambda x, y: cmp(x.name.lower(), y.name.lower())))
  754.         qrn = 1 + 4
  755.         dchunk = StringIO()
  756.         dcount = 0
  757.         quickref = []
  758.         name = directory[0].name
  759.         for entry in directory:
  760.             next = ''.join([
  761.                 decint(len(entry.name)),
  762.                 entry.name,
  763.                 decint(entry.section),
  764.                 decint(entry.offset),
  765.                 decint(entry.size)])
  766.             usedlen = dchunk.tell() + len(next) + len(quickref) * 2 + 52
  767.             if usedlen >= DCHUNK_SIZE:
  768.                 ddata.append((dchunk.getvalue(), quickref, dcount, name))
  769.                 dchunk = StringIO()
  770.                 dcount = 0
  771.                 quickref = []
  772.                 name = entry.name
  773.             
  774.             if dcount % qrn == 0:
  775.                 quickref.append(dchunk.tell())
  776.             
  777.             dchunk.write(next)
  778.             dcount = dcount + 1
  779.         
  780.         ddata.append((dchunk.getvalue(), quickref, dcount, name))
  781.         cidmax = len(ddata) - 1
  782.         rdcount = 0
  783.         dchunks = []
  784.         dcounts = []
  785.         ichunk = None
  786.         if len(ddata) > 1:
  787.             ichunk = StringIO()
  788.         
  789.         for content, quickref, dcount, name in izip(count(), ddata):
  790.             dchunk = StringIO()
  791.             prev = None if cid > 0 else ULL_NEG1
  792.             next = None if cid < cidmax else ULL_NEG1
  793.             rem = DCHUNK_SIZE - (len(content) + 50)
  794.             pad = rem - len(quickref) * 2
  795.             dchunk.write('AOLL')
  796.             dchunk.write(pack('<IQQQQQ', rem, cid, prev, next, rdcount, 1))
  797.             dchunk.write(content)
  798.             dchunk.write('\x00' * pad)
  799.             for ref in reversed(quickref):
  800.                 dchunk.write(pack('<H', ref))
  801.             
  802.             dchunk.write(pack('<H', dcount))
  803.             rdcount = rdcount + dcount
  804.             dchunks.append(dchunk.getvalue())
  805.             dcounts.append(dcount)
  806.             if ichunk:
  807.                 ichunk.write(decint(len(name)))
  808.                 ichunk.write(name)
  809.                 ichunk.write(decint(cid))
  810.                 continue
  811.         
  812.         if ichunk:
  813.             rem = DCHUNK_SIZE - (ichunk.tell() + 16)
  814.             pad = rem - 2
  815.             ichunk = ''.join([
  816.                 'AOLI',
  817.                 pack('<IQ', rem, len(dchunks)),
  818.                 ichunk.getvalue(),
  819.                 '\x00' * pad,
  820.                 pack('<H', len(dchunks))])
  821.         
  822.         return (dcounts, dchunks, ichunk)
  823.  
  824.  
  825.