| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 # This Source Code Form is subject to the terms of the Mozilla Public | 
|  | 2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 
|  | 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
|  | 4 | 
|  | 5 import base64 | 
|  | 6 import hashlib | 
|  | 7 import os | 
|  | 8 import zipfile | 
|  | 9 | 
|  | 10 import packager | 
|  | 11 import packagerChrome | 
|  | 12 | 
|  | 13 MANIFEST = 'AppxManifest.xml' | 
|  | 14 CONTENT_TYPES = '[Content_Types].xml' | 
|  | 15 BLOCKMAP = 'AppxBlockMap.xml' | 
|  | 16 BLOCKSIZE = 64 * 1024 | 
|  | 17 | 
|  | 18 | 
|  | 19 def _get_template_for(filename): | 
|  | 20     return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
|  | 21 | 
|  | 22 | 
|  | 23 def _SHA256(block): | 
|  | 24     h = hashlib.new('sha256') | 
|  | 25     h.update(block) | 
|  | 26     return base64.b64encode(h.digest()) | 
|  | 27 | 
|  | 28 | 
|  | 29 def _lfh_size(filename): | 
|  | 30     """Compute the size of zip local file header for `filename`.""" | 
|  | 31     try: | 
|  | 32         filename = filename.encode('utf-8') | 
|  | 33     except UnicodeDecodeError: | 
|  | 34         pass  # filename is already a byte string. | 
|  | 35     return zipfile.sizeFileHeader + len(filename) | 
|  | 36 | 
|  | 37 | 
|  | 38 def _make_blockmap_entry(filename, data): | 
|  | 39     blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)] | 
|  | 40     return { | 
|  | 41         'name': filename.replace('/', '\\'), | 
|  | 42         'size': len(data), | 
|  | 43         'lfh_size': _lfh_size(filename), | 
|  | 44         'blocks': [{'hash': _SHA256(block), 'compressed_size': len(block)} | 
|  | 45                    for block in blocks] | 
|  | 46     } | 
|  | 47 | 
|  | 48 | 
|  | 49 def createAppxBlockmap(files): | 
|  | 50     """Create APPX blockmap for the list of files.""" | 
|  | 51     template = _get_template_for(BLOCKMAP) | 
|  | 52     files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 
|  | 53     return template.render(files=files).encode('utf-8') | 
|  | 54 | 
|  | 55 | 
|  | 56 def createAppxManifest(params, files): | 
|  | 57     """Create AppxManifest.xml.""" | 
|  | 58     template = _get_template_for(MANIFEST) | 
|  | 59     params = dict(params) | 
|  | 60     metadata = params['metadata'] | 
|  | 61     params['package_identity'] = dict(metadata.items('package_identity')) | 
|  | 62     params.update(metadata.items('general')) | 
|  | 63     for size in ['44', '50', '150']: | 
|  | 64         path = 'Assets/logo_{}.png'.format(size) | 
|  | 65         if path not in files: | 
|  | 66             raise KeyError('{} is not found in files'.format(path)) | 
|  | 67         params['logo_{}'.format(size)] = path.replace('/', '\\') | 
|  | 68     return template.render(params).encode('utf-8') | 
|  | 69 | 
|  | 70 | 
|  | 71 def moveFilesToExtension(files): | 
|  | 72     """Move all files into `Extension` folder for APPX packaging.""" | 
|  | 73     names = sorted(files.keys(), key=len, reverse=True) | 
|  | 74     for filename in names: | 
|  | 75         files['Extension/' + filename] = files[filename] | 
|  | 76         del files[filename] | 
|  | 77 | 
|  | 78 | 
|  | 79 def createContentTypesMap(): | 
|  | 80     """Create [Content_Types].xml -- a mime type map.""" | 
|  | 81     content_types_template = _get_template_for(CONTENT_TYPES) | 
|  | 82     return content_types_template.render().encode('utf-8') | 
|  | 83 | 
|  | 84 | 
|  | 85 class Files(packager.Files): | 
|  | 86     """Files subclass that zips without compression.""" | 
|  | 87 | 
|  | 88     # We don't support AppxBlockmap.xml generation for compressed zip files at | 
|  | 89     # the moment. The only way to reliably calculate the compressed size of | 
|  | 90     # each 64k chunk in the zip file is to override the relevant parts of | 
|  | 91     # `zipfile` library. We have chosen to not do it for now, so zip() below | 
|  | 92     # doesn't perform any compression. | 
|  | 93 | 
|  | 94     # TODO: Replace zip() below with a compressing version: | 
|  | 95     # https://issues.adblockplus.org/ticket/4149 | 
|  | 96 | 
|  | 97     def zip(self, outFile, sortKey=None): | 
|  | 98         """Pack files into zip archive producing matching appx block map.""" | 
|  | 99         zf = zipfile.ZipFile(outFile, 'w', zipfile.ZIP_STORED) | 
|  | 100         for name in sorted(self, key=sortKey): | 
|  | 101             zf.writestr(name, self[name]) | 
|  | 102         zf.writestr(BLOCKMAP, createAppxBlockmap(self)) | 
|  | 103         zf.close() | 
|  | 104 | 
|  | 105 | 
|  | 106 def createBuild(baseDir, type='edge', outFile=None, buildNum=None, | 
|  | 107                 releaseBuild=False, keyFile=None, devenv=False): | 
|  | 108 | 
|  | 109     metadata = packager.readMetadata(baseDir, type) | 
|  | 110     version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 
|  | 111                                        buildNum) | 
|  | 112 | 
|  | 113     if outFile is None: | 
|  | 114         outFile = packager.getDefaultFileName(metadata, version, 'appx') | 
|  | 115 | 
|  | 116     params = { | 
|  | 117         'type': type, | 
|  | 118         'baseDir': baseDir, | 
|  | 119         'releaseBuild': releaseBuild, | 
|  | 120         'version': version, | 
|  | 121         'devenv': devenv, | 
|  | 122         'metadata': metadata, | 
|  | 123     } | 
|  | 124 | 
|  | 125     files = Files(packagerChrome.getPackageFiles(params), | 
|  | 126                   packagerChrome.getIgnoredFiles(params)) | 
|  | 127 | 
|  | 128     if metadata.has_section('mapping'): | 
|  | 129         mapped = metadata.items('mapping') | 
|  | 130         files.readMappedFiles(mapped) | 
|  | 131         files.read(baseDir, skip=[filename for filename, _ in mapped]) | 
|  | 132     else: | 
|  | 133         files.read(baseDir) | 
|  | 134 | 
|  | 135     if metadata.has_section('convert_js'): | 
|  | 136         packagerChrome.convertJS(params, files) | 
|  | 137 | 
|  | 138     if metadata.has_section('preprocess'): | 
|  | 139         files.preprocess(metadata.options('preprocess'), {'needsExt': True}) | 
|  | 140 | 
|  | 141     if metadata.has_section('import_locales'): | 
|  | 142         packagerChrome.importGeckoLocales(params, files) | 
|  | 143 | 
|  | 144     files['manifest.json'] = packagerChrome.createManifest(params, files) | 
|  | 145 | 
|  | 146     moveFilesToExtension(files) | 
|  | 147 | 
|  | 148     if metadata.has_section('appx_assets'): | 
|  | 149         for name, path in metadata.items('appx_assets'): | 
|  | 150             path = os.path.join(baseDir, path) | 
|  | 151             files.read(path, 'Assets/{}'.format(name)) | 
|  | 152 | 
|  | 153     files[MANIFEST] = createAppxManifest(params, files) | 
|  | 154     files[CONTENT_TYPES] = createContentTypesMap() | 
|  | 155 | 
|  | 156     files.zip(outFile) | 
| OLD | NEW | 
|---|