| LEFT | RIGHT | 
|---|
| 1 # This Source Code Form is subject to the terms of the Mozilla Public | 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 | 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/. | 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
| 4 | 4 | 
| 5 import base64 | 5 import base64 | 
| 6 import hashlib | 6 import hashlib | 
|  | 7 import json | 
| 7 import mimetypes | 8 import mimetypes | 
| 8 import os | 9 import os | 
| 9 import zipfile | 10 import zipfile | 
| 10 | 11 | 
| 11 import packager | 12 import packager | 
| 12 import packagerChrome | 13 import packagerChrome | 
| 13 | 14 | 
|  | 15 # Files and directories expected inside of the .APPX archive. | 
| 14 MANIFEST = 'AppxManifest.xml' | 16 MANIFEST = 'AppxManifest.xml' | 
| 15 CONTENT_TYPES = '[Content_Types].xml' | 17 CONTENT_TYPES = '[Content_Types].xml' | 
| 16 BLOCKMAP = 'AppxBlockMap.xml' | 18 BLOCKMAP = 'AppxBlockMap.xml' | 
|  | 19 EXTENSION_DIR = 'Extension' | 
|  | 20 ASSETS_DIR = 'Assets' | 
|  | 21 | 
|  | 22 # Size of uncompressed block in the APPX block map. | 
| 17 BLOCKSIZE = 64 * 1024 | 23 BLOCKSIZE = 64 * 1024 | 
|  | 24 | 
|  | 25 defaultLocale = packagerChrome.defaultLocale | 
| 18 | 26 | 
| 19 | 27 | 
| 20 def _get_template_for(filename): | 28 def _get_template_for(filename): | 
| 21     return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 29     return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
| 22 | 30 | 
| 23 | 31 | 
| 24 def _lfh_size(filename): | 32 def _lfh_size(filename): | 
| 25     """Compute the size of zip local file header for `filename`.""" | 33     """Compute the size of zip local file header for `filename`.""" | 
| 26     try: | 34     try: | 
| 27         filename = filename.encode('utf-8') | 35         filename = filename.encode('utf-8') | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
| 38         'lfh_size': _lfh_size(filename), | 46         'lfh_size': _lfh_size(filename), | 
| 39         'blocks': [ | 47         'blocks': [ | 
| 40             {'hash': base64.b64encode(hashlib.sha256(block).digest())} | 48             {'hash': base64.b64encode(hashlib.sha256(block).digest())} | 
| 41             for block in blocks | 49             for block in blocks | 
| 42         ] | 50         ] | 
| 43     } | 51     } | 
| 44 | 52 | 
| 45 | 53 | 
| 46 def create_appx_blockmap(files): | 54 def create_appx_blockmap(files): | 
| 47     """Create APPX blockmap for the list of files.""" | 55     """Create APPX blockmap for the list of files.""" | 
|  | 56     # We don't support AppxBlockmap.xml generation for compressed zip files at | 
|  | 57     # the moment. The only way to reliably calculate the compressed size of | 
|  | 58     # each 64k chunk in the zip file is to override the relevant parts of | 
|  | 59     # `zipfile` library. We have chosen to not do it so we produce an | 
|  | 60     # uncompressed zip file that is later repackaged by Windows Store with | 
|  | 61     # compression. | 
| 48     template = _get_template_for(BLOCKMAP) | 62     template = _get_template_for(BLOCKMAP) | 
| 49     files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 63     files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 
| 50     return template.render(files=files).encode('utf-8') | 64     return template.render(files=files).encode('utf-8') | 
| 51 | 65 | 
| 52 | 66 | 
| 53 def create_appx_manifest(params, files): | 67 def load_translation(files, locale): | 
|  | 68     """Load translation strings for locale from files.""" | 
|  | 69     path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale) | 
|  | 70     return json.loads(files[path]) | 
|  | 71 | 
|  | 72 | 
|  | 73 def pad_version(version): | 
|  | 74     """Make sure version number has 4 groups of digits.""" | 
|  | 75     groups = (version.split('.') + ['0', '0', '0'])[:4] | 
|  | 76     return '.'.join(groups) | 
|  | 77 | 
|  | 78 | 
|  | 79 def create_appx_manifest(params, files, release_build=False): | 
| 54     """Create AppxManifest.xml.""" | 80     """Create AppxManifest.xml.""" | 
| 55     template = _get_template_for(MANIFEST) |  | 
| 56     params = dict(params) | 81     params = dict(params) | 
| 57     metadata = params['metadata'] | 82     metadata = params['metadata'] | 
| 58     params['package_identity'] = dict(metadata.items('package_identity')) |  | 
| 59     w = params['windows_version'] = {} | 83     w = params['windows_version'] = {} | 
| 60     w['min'], w['max'] = metadata.get('compat', 'windows').split('/') | 84     w['min'], w['max'] = metadata.get('compat', 'windows').split('/') | 
| 61     params.update(metadata.items('general')) | 85     params.update(metadata.items('general')) | 
|  | 86     params['version'] = pad_version(params['version']) | 
|  | 87 | 
|  | 88     translation = load_translation(files, defaultLocale) | 
|  | 89     name_key = 'name' if release_build else 'name_devbuild' | 
|  | 90     params['display_name'] = translation[name_key]['message'] | 
|  | 91     params['description'] = translation['description']['message'] | 
|  | 92 | 
| 62     for size in ['44', '50', '150']: | 93     for size in ['44', '50', '150']: | 
| 63         path = 'Assets/logo_{}.png'.format(size) | 94         path = '{}/logo_{}.png'.format(ASSETS_DIR, size) | 
| 64         if path not in files: | 95         if path not in files: | 
| 65             raise KeyError(path + 'is not found in files') | 96             raise KeyError(path + 'is not found in files') | 
| 66         params['logo_' + size] = path.replace('/', '\\') | 97         params['logo_' + size] = path.replace('/', '\\') | 
|  | 98 | 
|  | 99     template = _get_template_for(MANIFEST) | 
| 67     return template.render(params).encode('utf-8') | 100     return template.render(params).encode('utf-8') | 
| 68 | 101 | 
| 69 | 102 | 
| 70 def move_files_to_extension(files): | 103 def move_files_to_extension(files): | 
| 71     """Move all files into `Extension` folder for APPX packaging.""" | 104     """Move all files into `Extension` folder for APPX packaging.""" | 
| 72     # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. | 105     # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. | 
| 73     # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its | 106     # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its | 
| 74     # original content would be lost. | 107     # original content would be lost. | 
| 75     names = sorted(files.keys(), key=len, reverse=True) | 108     names = sorted(files.keys(), key=len, reverse=True) | 
| 76     for filename in names: | 109     for filename in names: | 
| 77         files['Extension/' + filename] = files.pop(filename) | 110         files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename) | 
| 78 | 111 | 
| 79 | 112 | 
| 80 def create_content_types_map(filenames): | 113 def create_content_types_map(filenames): | 
| 81     """Create [Content_Types].xml -- a mime type map.""" | 114     """Create [Content_Types].xml -- a mime type map.""" | 
| 82     params = {'defaults': {}, 'overrides': {}} | 115     params = {'defaults': {}, 'overrides': {}} | 
| 83     overrides = { | 116     overrides = { | 
| 84         BLOCKMAP: 'application/vnd.ms-appx.blockmap+xml', | 117         BLOCKMAP: 'application/vnd.ms-appx.blockmap+xml', | 
| 85         MANIFEST: 'application/vnd.ms-appx.manifest+xml' | 118         MANIFEST: 'application/vnd.ms-appx.manifest+xml' | 
| 86     } | 119     } | 
| 87     for filename in filenames: | 120     for filename in filenames: | 
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 134     if metadata.has_section('import_locales'): | 167     if metadata.has_section('import_locales'): | 
| 135         packagerChrome.importGeckoLocales(params, files) | 168         packagerChrome.importGeckoLocales(params, files) | 
| 136 | 169 | 
| 137     files['manifest.json'] = packagerChrome.createManifest(params, files) | 170     files['manifest.json'] = packagerChrome.createManifest(params, files) | 
| 138 | 171 | 
| 139     move_files_to_extension(files) | 172     move_files_to_extension(files) | 
| 140 | 173 | 
| 141     if metadata.has_section('appx_assets'): | 174     if metadata.has_section('appx_assets'): | 
| 142         for name, path in metadata.items('appx_assets'): | 175         for name, path in metadata.items('appx_assets'): | 
| 143             path = os.path.join(baseDir, path) | 176             path = os.path.join(baseDir, path) | 
| 144             files.read(path, 'Assets/' + name) | 177             files.read(path, '{}/{}'.format(ASSETS_DIR, name)) | 
| 145 | 178 | 
| 146     files[MANIFEST] = create_appx_manifest(params, files) | 179     files[MANIFEST] = create_appx_manifest(params, files, releaseBuild) | 
|  | 180     files[BLOCKMAP] = create_appx_blockmap(files) | 
| 147     files[CONTENT_TYPES] = create_content_types_map(files.keys() + [BLOCKMAP]) | 181     files[CONTENT_TYPES] = create_content_types_map(files.keys() + [BLOCKMAP]) | 
| 148 | 182 | 
| 149     # We don't support AppxBlockmap.xml generation for compressed zip files at | 183     files.zip(outfile, compression=zipfile.ZIP_STORED) | 
| 150     # the moment. The only way to reliably calculate the compressed size of |  | 
| 151     # each 64k chunk in the zip file is to override the relevant parts of |  | 
| 152     # `zipfile` library. We have chosen to not do it for now, so we produce |  | 
| 153     # an uncompressed zip file. |  | 
| 154     files[BLOCKMAP] = create_appx_blockmap(files) |  | 
| 155 |  | 
| 156     # TODO: Implement AppxBlockmap.xml generation for compressed zip files. |  | 
| 157     # https://issues.adblockplus.org/ticket/4149 |  | 
| 158     files.zip(outfile, compress=False) |  | 
| LEFT | RIGHT | 
|---|