| LEFT | RIGHT |
| 1 # coding: utf-8 | 1 # coding: utf-8 |
| 2 | 2 |
| 3 # This file is part of the Adblock Plus build tools, | 3 # This file is part of the Adblock Plus build tools, |
| 4 # Copyright (C) 2006-2013 Eyeo GmbH | 4 # Copyright (C) 2006-2013 Eyeo GmbH |
| 5 # | 5 # |
| 6 # Adblock Plus is free software: you can redistribute it and/or modify | 6 # Adblock Plus is free software: you can redistribute it and/or modify |
| 7 # it under the terms of the GNU General Public License version 3 as | 7 # it under the terms of the GNU General Public License version 3 as |
| 8 # published by the Free Software Foundation. | 8 # published by the Free Software Foundation. |
| 9 # | 9 # |
| 10 # Adblock Plus is distributed in the hope that it will be useful, | 10 # Adblock Plus is distributed in the hope that it will be useful, |
| 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 # GNU General Public License for more details. | 13 # GNU General Public License for more details. |
| 14 # | 14 # |
| 15 # You should have received a copy of the GNU General Public License | 15 # You should have received a copy of the GNU General Public License |
| 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 17 | 17 |
| 18 import sys, os, re, json, struct | 18 import sys, os, re, json, struct |
| 19 from StringIO import StringIO | 19 from StringIO import StringIO |
| 20 | 20 |
| 21 import PIL.Image | |
| 22 import PIL.ImageMath | |
| 23 | |
| 24 import packager | 21 import packager |
| 25 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild
Version, getTemplate, Files | 22 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild
Version, getTemplate, Files |
| 26 | 23 |
| 27 defaultLocale = 'en_US' | 24 defaultLocale = 'en_US' |
| 28 | 25 |
| 29 def getIgnoredFiles(params): | 26 def getIgnoredFiles(params): |
| 30 result = set(('store.description',)) | 27 result = set(('store.description',)) |
| 31 | 28 |
| 32 # Hack: ignore all lib subdirectories | 29 # Hack: ignore all lib subdirectories |
| 33 libDir = os.path.join(params['baseDir'], 'lib') | 30 libDir = os.path.join(params['baseDir'], 'lib') |
| 34 for file in os.listdir(libDir): | 31 for file in os.listdir(libDir): |
| 35 if os.path.isdir(os.path.join(libDir, file)): | 32 if os.path.isdir(os.path.join(libDir, file)): |
| 36 result.add(file) | 33 result.add(file) |
| 37 return result | 34 return result |
| 38 | 35 |
| 39 def getPackageFiles(params): | 36 def getPackageFiles(params): |
| 40 result = set(('_locales', 'icons', 'jquery-ui', 'lib', 'skin', 'ui',)) | 37 result = set(('_locales', 'icons', 'jquery-ui', 'lib', 'skin', 'ui', 'ext')) |
| 41 | 38 |
| 42 if params['devenv']: | 39 if params['devenv']: |
| 43 result.add('qunit') | 40 result.add('qunit') |
| 44 | 41 |
| 45 baseDir = params['baseDir'] | 42 baseDir = params['baseDir'] |
| 46 for file in os.listdir(baseDir): | 43 for file in os.listdir(baseDir): |
| 47 if file.endswith('.js') or file.endswith('.html') or file.endswith('.xml'): | 44 if file.endswith('.js') or file.endswith('.html') or file.endswith('.xml'): |
| 48 result.add(file) | 45 result.add(file) |
| 49 return result | 46 return result |
| 50 | 47 |
| (...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 275 'pt': 'pt_PT', | 272 'pt': 'pt_PT', |
| 276 } | 273 } |
| 277 for chromeLocale, operaLocale in operaMapping.iteritems(): | 274 for chromeLocale, operaLocale in operaMapping.iteritems(): |
| 278 chromeFile = '_locales/%s/messages.json' % chromeLocale | 275 chromeFile = '_locales/%s/messages.json' % chromeLocale |
| 279 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No
ne else None | 276 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No
ne else None |
| 280 if chromeFile in files: | 277 if chromeFile in files: |
| 281 if operaFile != None: | 278 if operaFile != None: |
| 282 files[operaFile] = files[chromeFile] | 279 files[operaFile] = files[chromeFile] |
| 283 del files[chromeFile] | 280 del files[chromeFile] |
| 284 | 281 |
| 285 if params['type'] in ('opera', 'safari'): | |
| 286 # Hack: Replace "Chrome" by "Opera" or "Safari" in the locales | |
| 287 for path, data in files.iteritems(): | |
| 288 if path.startswith("_locales/") and path.endswith("/messages.json"): | |
| 289 files[path] = re.sub(r"\bChrome\b", params['type'].capitalize(), data) | |
| 290 | |
| 291 def signBinary(zipdata, keyFile): | 282 def signBinary(zipdata, keyFile): |
| 292 import M2Crypto | 283 import M2Crypto |
| 293 if not os.path.exists(keyFile): | 284 if not os.path.exists(keyFile): |
| 294 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile,
cipher=None) | 285 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile,
cipher=None) |
| 295 key = M2Crypto.EVP.load_key(keyFile) | 286 key = M2Crypto.EVP.load_key(keyFile) |
| 296 key.sign_init() | 287 key.sign_init() |
| 297 key.sign_update(zipdata) | 288 key.sign_update(zipdata) |
| 298 return key.final() | 289 return key.final() |
| 299 | 290 |
| 300 def getPublicKey(keyFile): | 291 def getPublicKey(keyFile): |
| 301 import M2Crypto | 292 import M2Crypto |
| 302 return M2Crypto.EVP.load_key(keyFile).as_der() | 293 return M2Crypto.EVP.load_key(keyFile).as_der() |
| 303 | 294 |
| 304 def writePackage(outputFile, pubkey, signature, zipdata): | 295 def writePackage(outputFile, pubkey, signature, zipdata): |
| 305 if isinstance(outputFile, basestring): | 296 if isinstance(outputFile, basestring): |
| 306 file = open(outputFile, 'wb') | 297 file = open(outputFile, 'wb') |
| 307 else: | 298 else: |
| 308 file = outputFile | 299 file = outputFile |
| 309 if pubkey != None and signature != None: | 300 if pubkey != None and signature != None: |
| 310 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) | 301 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) |
| 311 file.write(pubkey) | 302 file.write(pubkey) |
| 312 file.write(signature) | 303 file.write(signature) |
| 313 file.write(zipdata) | 304 file.write(zipdata) |
| 314 | 305 |
| 315 class ImageConverter(object): | |
| 316 def convert(self, params, files): | |
| 317 for filename, chain in params['metadata'].items('convert_img'): | |
| 318 steps = re.split(r'\s*->\s*', chain) | |
| 319 image = PIL.Image.open(steps.pop(0)) | |
| 320 | |
| 321 for step in steps: | |
| 322 filter, args = re.match(r'([^(]+)(?:\((.*)\))?', step).groups() | |
| 323 args = tuple(re.split(r'\s*,\s*', args)) if args else () | |
| 324 image = getattr(self, 'filter_' + filter)(image, *args) | |
| 325 | |
| 326 f = StringIO() | |
| 327 f.name = filename | |
| 328 image.save(f) | |
| 329 files[filename] = f.getvalue() | |
| 330 | |
| 331 def filter_blend(self, image, *args): | |
| 332 if len(args) == 2: # args = (filename, opacity) | |
| 333 overlay = PIL.Image.open(args[0]) | |
| 334 | |
| 335 if image.mode != overlay.mode or image.mode == 'P': | |
| 336 overlay = overlay.convert('RGBA') | |
| 337 image = image.convert('RGBA') | |
| 338 elif len(args) == 4: # args = (red, green, blue, opacity) | |
| 339 overlay = PIL.Image.new('RGB', image.size, tuple(map(int, args[:3]))) | |
| 340 | |
| 341 if image.mode == 'P': | |
| 342 image = image.convert('RGBA') | |
| 343 | |
| 344 if image.mode in ('RGBA', 'LA'): | |
| 345 overlay = PIL.Image.merge('RGBA', overlay.split() + image.split()[-1:]) | |
| 346 | |
| 347 if image.mode != overlay.mode: | |
| 348 image = image.convert(overlay.mode) | |
| 349 else: | |
| 350 raise TypeError | |
| 351 | |
| 352 return PIL.Image.blend(image, overlay, float(args[-1])) | |
| 353 | |
| 354 def filter_grayscale(self, image): | |
| 355 if image.mode == 'P': | |
| 356 image = image.convert('RGBA') | |
| 357 | |
| 358 bands = list(image.split()) | |
| 359 alpha = bands.pop(-1) if image.mode in ('RGBA', 'LA') else None | |
| 360 | |
| 361 if len(bands) == 1: | |
| 362 return image | |
| 363 | |
| 364 new_image = PIL.ImageMath.eval( | |
| 365 "convert((%s) / %d, 'L')" % ( | |
| 366 ' + '.join('image%d' % i for i in xrange(len(bands))), | |
| 367 len(bands) | |
| 368 ), | |
| 369 **dict(('image%d' % i, band) for i, band in enumerate(bands)) | |
| 370 ) | |
| 371 | |
| 372 if alpha: | |
| 373 new_image = PIL.Image.merge('LA', [new_image, alpha]) | |
| 374 | |
| 375 return new_image | |
| 376 | |
| 377 def filter_colorToAlpha(self, image, *color): | |
| 378 bands = [band.convert('F') for band in image.convert('RGBA').split()] | |
| 379 color = map(float, color) | |
| 380 | |
| 381 # Find the maximum difference rate between source and color. I had to use tw
o | |
| 382 # difference functions because ImageMath.eval only evaluates the expression | |
| 383 # once. | |
| 384 alpha = PIL.ImageMath.eval('''\ | |
| 385 float( | |
| 386 max( | |
| 387 max( | |
| 388 max( | |
| 389 difference1(red_band, cred_band), | |
| 390 difference1(green_band, cgreen_band) | |
| 391 ), | |
| 392 difference1(blue_band, cblue_band) | |
| 393 ), | |
| 394 max( | |
| 395 max( | |
| 396 difference2(red_band, cred_band), | |
| 397 difference2(green_band, cgreen_band) | |
| 398 ), | |
| 399 difference2(blue_band, cblue_band) | |
| 400 ) | |
| 401 ) | |
| 402 )''', | |
| 403 difference1=lambda source, col: (source - col) / (255.0 - col), | |
| 404 difference2=lambda source, col: (col - source) / col, | |
| 405 | |
| 406 red_band = bands[0], | |
| 407 green_band= bands[1], | |
| 408 blue_band = bands[2], | |
| 409 | |
| 410 cred_band = color[0], | |
| 411 cgreen_band= color[1], | |
| 412 cblue_band = color[2], | |
| 413 ) | |
| 414 | |
| 415 # Calculate the new image colors after the removal of the selected color | |
| 416 new_bands = [ | |
| 417 PIL.ImageMath.eval( | |
| 418 "convert((image - color) / alpha + color, 'L')", | |
| 419 image=band, | |
| 420 color=col, | |
| 421 alpha=alpha | |
| 422 ) | |
| 423 for band, col in zip(bands, color) | |
| 424 ] | |
| 425 | |
| 426 # Add the new alpha band | |
| 427 new_bands.append(PIL.ImageMath.eval( | |
| 428 "convert(alpha_band * alpha, 'L')", | |
| 429 alpha = alpha, | |
| 430 alpha_band = bands[3] | |
| 431 )) | |
| 432 | |
| 433 return PIL.Image.merge('RGBA', new_bands) | |
| 434 | |
| 435 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil
d=False, keyFile=None, experimentalAPI=False, devenv=False): | 306 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil
d=False, keyFile=None, experimentalAPI=False, devenv=False): |
| 436 metadata = readMetadata(baseDir, type) | 307 metadata = readMetadata(baseDir, type) |
| 437 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | 308 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) |
| 438 | 309 |
| 439 if outFile == None: | 310 if outFile == None: |
| 440 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el
se 'zip') | 311 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el
se 'zip') |
| 441 | 312 |
| 442 params = { | 313 params = { |
| 443 'type': type, | 314 'type': type, |
| 444 'baseDir': baseDir, | 315 'baseDir': baseDir, |
| 445 'releaseBuild': releaseBuild, | 316 'releaseBuild': releaseBuild, |
| 446 'version': version, | 317 'version': version, |
| 447 'experimentalAPI': experimentalAPI, | 318 'experimentalAPI': experimentalAPI, |
| 448 'devenv': devenv, | 319 'devenv': devenv, |
| 449 'metadata': metadata, | 320 'metadata': metadata, |
| 450 } | 321 } |
| 451 | 322 |
| 452 files = Files(getPackageFiles(params), getIgnoredFiles(params), | 323 files = Files(getPackageFiles(params), getIgnoredFiles(params), |
| 453 process=lambda path, data: processFile(path, data, params)) | 324 process=lambda path, data: processFile(path, data, params)) |
| 454 files['manifest.json'] = createManifest(params) | 325 files['manifest.json'] = createManifest(params) |
| 455 if metadata.has_section('mapping'): | 326 if metadata.has_section('mapping'): |
| 456 files.readMappedFiles(metadata.items('mapping')) | 327 files.readMappedFiles(metadata.items('mapping')) |
| 457 files.read(baseDir) | 328 files.read(baseDir) |
| 458 | 329 |
| 459 if metadata.has_section('convert_js'): | 330 if metadata.has_section('convert_js'): |
| 460 convertJS(params, files) | 331 convertJS(params, files) |
| 461 | 332 |
| 462 if metadata.has_section('convert_img'): | 333 if metadata.has_section('convert_img'): |
| 463 ImageConverter().convert(params, files) | 334 from imageConversion import convertImages |
| 335 convertImages(params, files) |
| 336 |
| 337 if metadata.has_section('preprocess'): |
| 338 files.preprocess( |
| 339 [f for f, _ in metadata.items('preprocess')], |
| 340 {'needsExt': True} |
| 341 ) |
| 464 | 342 |
| 465 if metadata.has_section('import_locales'): | 343 if metadata.has_section('import_locales'): |
| 466 importGeckoLocales(params, files) | 344 importGeckoLocales(params, files) |
| 467 | 345 |
| 468 if devenv: | 346 if devenv: |
| 469 files['devenvPoller__.js'] = createPoller(params) | 347 files['devenvPoller__.js'] = createPoller(params) |
| 470 | 348 |
| 471 if (metadata.has_option('general', 'backgroundScripts') and | 349 if (metadata.has_option('general', 'backgroundScripts') and |
| 472 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip
ts')) and | 350 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip
ts')) and |
| 473 'lib/info.js' not in files): | 351 'lib/info.js' not in files): |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 506 def shutdown_server(server): | 384 def shutdown_server(server): |
| 507 time.sleep(10) | 385 time.sleep(10) |
| 508 server.shutdown() | 386 server.shutdown() |
| 509 thread.start_new_thread(shutdown_server, (server,)) | 387 thread.start_new_thread(shutdown_server, (server,)) |
| 510 server.serve_forever() | 388 server.serve_forever() |
| 511 | 389 |
| 512 if connections[0] == 0: | 390 if connections[0] == 0: |
| 513 print 'Warning: No incoming connections, extension probably not active in th
e browser yet' | 391 print 'Warning: No incoming connections, extension probably not active in th
e browser yet' |
| 514 else: | 392 else: |
| 515 print 'Handled %i connection(s)' % connections[0] | 393 print 'Handled %i connection(s)' % connections[0] |
| LEFT | RIGHT |