| 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 json | 
 |    8 import mimetypes | 
 |    9 import os | 
 |   10 import zipfile | 
 |   11  | 
 |   12 import packager | 
 |   13 import packagerChrome | 
 |   14  | 
 |   15 # Files and directories expected inside of the .APPX archive. | 
 |   16 MANIFEST = 'AppxManifest.xml' | 
 |   17 CONTENT_TYPES = '[Content_Types].xml' | 
 |   18 BLOCKMAP = 'AppxBlockMap.xml' | 
 |   19 EXTENSION_DIR = 'Extension' | 
 |   20 ASSETS_DIR = 'Assets' | 
 |   21  | 
 |   22 # Size of uncompressed block in the APPX block map. | 
 |   23 BLOCKSIZE = 64 * 1024 | 
 |   24  | 
 |   25 defaultLocale = packagerChrome.defaultLocale | 
 |   26  | 
 |   27  | 
 |   28 def _get_template_for(filename): | 
 |   29     return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
 |   30  | 
 |   31  | 
 |   32 def _lfh_size(filename): | 
 |   33     """Compute the size of zip local file header for `filename`.""" | 
 |   34     try: | 
 |   35         filename = filename.encode('utf-8') | 
 |   36     except UnicodeDecodeError: | 
 |   37         pass  # filename is already a byte string. | 
 |   38     return zipfile.sizeFileHeader + len(filename) | 
 |   39  | 
 |   40  | 
 |   41 def _make_blockmap_entry(filename, data): | 
 |   42     blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)] | 
 |   43     return { | 
 |   44         'name': filename.replace('/', '\\'), | 
 |   45         'size': len(data), | 
 |   46         'lfh_size': _lfh_size(filename), | 
 |   47         'blocks': [ | 
 |   48             {'hash': base64.b64encode(hashlib.sha256(block).digest())} | 
 |   49             for block in blocks | 
 |   50         ] | 
 |   51     } | 
 |   52  | 
 |   53  | 
 |   54 def create_appx_blockmap(files): | 
 |   55     """Create APPX blockmap for the list of files.""" | 
 |   56     template = _get_template_for(BLOCKMAP) | 
 |   57     files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 
 |   58     return template.render(files=files).encode('utf-8') | 
 |   59  | 
 |   60  | 
 |   61 def load_translation(files, locale): | 
 |   62     """Load translation strings for locale from files.""" | 
 |   63     path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale) | 
 |   64     return json.loads(files[path]) | 
 |   65  | 
 |   66  | 
 |   67 def create_appx_manifest(params, files): | 
 |   68     """Create AppxManifest.xml.""" | 
 |   69     template = _get_template_for(MANIFEST) | 
 |   70     params = dict(params) | 
 |   71     translation = load_translation(files, defaultLocale) | 
 |   72     params['display_name'] = translation['name'] | 
 |   73     params['description'] = translation['description'] | 
 |   74     metadata = params['metadata'] | 
 |   75     params['package_identity'] = dict(metadata.items('package_identity')) | 
 |   76     w = params['windows_version'] = {} | 
 |   77     w['min'], w['max'] = metadata.get('compat', 'windows').split('/') | 
 |   78     params.update(metadata.items('general')) | 
 |   79     for size in ['44', '50', '150']: | 
 |   80         path = '{}/logo_{}.png'.format(ASSETS_DIR, size) | 
 |   81         if path not in files: | 
 |   82             raise KeyError(path + 'is not found in files') | 
 |   83         params['logo_' + size] = path.replace('/', '\\') | 
 |   84     return template.render(params).encode('utf-8') | 
 |   85  | 
 |   86  | 
 |   87 def move_files_to_extension(files): | 
 |   88     """Move all files into `Extension` folder for APPX packaging.""" | 
 |   89     # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. | 
 |   90     # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its | 
 |   91     # original content would be lost. | 
 |   92     names = sorted(files.keys(), key=len, reverse=True) | 
 |   93     for filename in names: | 
 |   94         files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename) | 
 |   95  | 
 |   96  | 
 |   97 def create_content_types_map(filenames): | 
 |   98     """Create [Content_Types].xml -- a mime type map.""" | 
 |   99     params = {'defaults': {}, 'overrides': {}} | 
 |  100     overrides = { | 
 |  101         BLOCKMAP: 'application/vnd.ms-appx.blockmap+xml', | 
 |  102         MANIFEST: 'application/vnd.ms-appx.manifest+xml' | 
 |  103     } | 
 |  104     for filename in filenames: | 
 |  105         ext = os.path.splitext(filename)[1] | 
 |  106         if ext: | 
 |  107             content_type = mimetypes.guess_type(filename, strict=False)[0] | 
 |  108             if content_type is not None: | 
 |  109                 params['defaults'][ext[1:]] = content_type | 
 |  110         if filename in overrides: | 
 |  111             params['overrides']['/' + filename] = overrides[filename] | 
 |  112     content_types_template = _get_template_for(CONTENT_TYPES) | 
 |  113     return content_types_template.render(params).encode('utf-8') | 
 |  114  | 
 |  115  | 
 |  116 def createBuild(baseDir, type='edge', outFile=None,  # noqa: preserve API. | 
 |  117                 buildNum=None, releaseBuild=False, keyFile=None, | 
 |  118                 devenv=False): | 
 |  119  | 
 |  120     metadata = packager.readMetadata(baseDir, type) | 
 |  121     version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 
 |  122                                        buildNum) | 
 |  123  | 
 |  124     outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx') | 
 |  125  | 
 |  126     params = { | 
 |  127         'type': type, | 
 |  128         'baseDir': baseDir, | 
 |  129         'releaseBuild': releaseBuild, | 
 |  130         'version': version, | 
 |  131         'devenv': devenv, | 
 |  132         'metadata': metadata, | 
 |  133     } | 
 |  134  | 
 |  135     files = packager.Files(packagerChrome.getPackageFiles(params), | 
 |  136                            packagerChrome.getIgnoredFiles(params)) | 
 |  137  | 
 |  138     if metadata.has_section('mapping'): | 
 |  139         mapped = metadata.items('mapping') | 
 |  140         files.readMappedFiles(mapped) | 
 |  141         files.read(baseDir, skip=[filename for filename, _ in mapped]) | 
 |  142     else: | 
 |  143         files.read(baseDir) | 
 |  144  | 
 |  145     if metadata.has_section('convert_js'): | 
 |  146         packagerChrome.convertJS(params, files) | 
 |  147  | 
 |  148     if metadata.has_section('preprocess'): | 
 |  149         files.preprocess(metadata.options('preprocess'), {'needsExt': True}) | 
 |  150  | 
 |  151     if metadata.has_section('import_locales'): | 
 |  152         packagerChrome.importGeckoLocales(params, files) | 
 |  153  | 
 |  154     files['manifest.json'] = packagerChrome.createManifest(params, files) | 
 |  155  | 
 |  156     move_files_to_extension(files) | 
 |  157  | 
 |  158     if metadata.has_section('appx_assets'): | 
 |  159         for name, path in metadata.items('appx_assets'): | 
 |  160             path = os.path.join(baseDir, path) | 
 |  161             files.read(path, '{}/{}'.format(ASSETS_DIR, name)) | 
 |  162  | 
 |  163     files[MANIFEST] = create_appx_manifest(params, files) | 
 |  164     files[CONTENT_TYPES] = create_content_types_map(files.keys() + [BLOCKMAP]) | 
 |  165  | 
 |  166     # We don't support AppxBlockmap.xml generation for compressed zip files at | 
 |  167     # the moment. The only way to reliably calculate the compressed size of | 
 |  168     # each 64k chunk in the zip file is to override the relevant parts of | 
 |  169     # `zipfile` library. We have chosen to not do it for now, so we produce | 
 |  170     # an uncompressed zip file. | 
 |  171     files[BLOCKMAP] = create_appx_blockmap(files) | 
 |  172  | 
 |  173     # TODO: Implement AppxBlockmap.xml generation for compressed zip files. | 
 |  174     # https://issues.adblockplus.org/ticket/4149 | 
 |  175     files.zip(outfile, compress=False) | 
| OLD | NEW |